Реализации плагина с использованием FFI
Пример платформо-зависимого плагина, работающего с операционной системой Аврора с использованием механизма Foreign function interface (FFI).
Задача плагина — получить название устройства с использованием FFI.
Реализация плагина через FFI позволяет работать с операционной системой без использования механизма Platform Channel. Это удобный способ для реализации простых платформо-зависимых плагинов.
Примечание:
Этот демонстрационный плагин доступен в репозитории Flutter в разделе examples/ffi.
Содержание:
Создание приложения
Плагин FFI является платформо-зависимым, то есть зависит от операционной системы. Подробнее ознакомиться с типами плагинов можно в разделе Пакеты Flutter. FFI позволяет использовать библиотеки с интерфесом, написанным на C. Flutter CLI позволяет создать шаблон плагина. Для генерации шаблона плагина можно выполнить следующую команду в терминале:
flutter-aurora create --template=plugin_ffi --platforms aurora --org=ru.aurora ffi_demo
ru.aurora— имя организации (Organization name), участвует в формировании названия пакета;ffi_demo— название плагина (Application Name), участвует в формировании названия пакета.
Данная команда генерирует базовый пример плагина Flutter с настроенным окружением для сборки под ОС Аврора. Структура файлов и каталогов проекта имеет следующий вид:
.
├── aurora
│ └── CMakeLists.txt
├── example
│ ├── aurora
│ │ ├── CMakeLists.txt
│ │ ├── desktop
│ │ │ └── ru.aurora.ffi_demo_example.desktop
│ │ ├── icons
│ │ │ ├── 108x108.png
│ │ │ ├── 128x128.png
│ │ │ ├── 172x172.png
│ │ │ └── 86x86.png
│ │ ├── main.cpp
│ │ └── rpm
│ │ └── ru.aurora.ffi_demo_example.spec
│ ├── lib
│ │ └── main.dart
│ ├── analysis_options.yaml
│ ├── ffi_demo_example.iml
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── README.md
├── lib
│ ├── ffi_demo_bindings_generated.dart
│ └── ffi_demo.dart
├── src
│ ├── CMakeLists.txt
│ ├── ffi_demo.c
│ └── ffi_demo.h
├── analysis_options.yaml
├── CHANGELOG.md
├── ffi_demo.iml
├── ffigen.yaml
├── LICENSE
├── pubspec.lock
├── pubspec.yaml
└── README.md
10 directories, 28 files
Данная структура является типичной для приложений на Flutter.
В директории src находится код библиотеки C++, которая будет подключена к плагину.
В директориях <project>/aurora и <project>/example/aurora находятся файлы, обеспечивающие работу плагина и приложения на платформе ОС Аврора:
CMakeLists.txt— приложение и плагины Flutter для ОС Аврора имеют платформенную часть на С++, а сборка реализована через CMake;desktop/ru.aurora.app_demo.desktop— файл с конфигурацией ярлыка приложения. В нём можно указать название приложения, нужные права для приложения и другие настройки;icons/*.png— иконки приложения;main.cpp— точка входа в приложение для ОС Аврора. Это зачастую шаблонный код для запуска всех необходимых компонентов Flutter и приложения;rpm/ru.aurora.app_demo.spec— файл с конфигурацией установочного пакета, который утилита rpmbuild использует для сборки RPM-пакета.
Примечание:
Для предварительного знакомства с Flutter можно обратиться к документации Flutter и создать своё первое приложение, используя информацию из статьи "Write your first Flutter app".
Настройка FFI
В файле pubspec.yaml плагина необходимо указать зависимость от плагина ffi:
dependencies:
ffi: ^2.0.2
Проверить наличие активации FFI в файле плагина pubspec.yaml:
flutter:
plugin:
platforms:
aurora:
ffiPlugin: true
Эта конфигурация вызывает встроенную сборку для различных целевых платформ и объединяет двоичные файлы в приложение Flutter.
Генерация привязок
Для генерации привязок в корне шаблона плагина находится файл ffigen.yaml.
Здесь описывается информация, с помощью которой можно создать привязки на языке Dart.
# Название класса в Dart после генерации
name: FfiDemoBindings
# Описание привязок
description: |
Bindings for `src/ffi_demo.h`.
# Путь к файлу который должен быть создан
output: 'lib/ffi_demo_bindings_generated.dart'
# Заголовочные файлы библиотеки, на данных которых создаются привязки
headers:
entry-points:
- 'src/ffi_demo.h'
На основе файла конфигурации можно выполнить генерацию привязок в файл <poject>/lib/ffi_demo_bindings_generated.dart
из корня плагина:
flutter-aurora pub run ffigen --config ffigen.yaml
Команда создаст файл <project>/lib/ffi_demo_bindings_generated.dart
с классом FfiDemoBindings, который можно использовать для доступа к библиотеке из кода Dart.
Доработка плагина
Демонстрационный плагин выполняет задачу по получению названия устройства.
Это можно осуществить через доступную службу интерфейса D-Bus
ru.omp.deviceinfo.
Подробнее со службой можно ознакомиться в документации ОС Аврора "Device Info API".
Для реализации задачи можно использовать QtDBus.
В директории src находится код на C или C++, созданный Flutter CLI при генерации шаблона плагина.
В файл CMakeLists.txt следует добавить зависимости, которые позволят получить доступ к QtDBus:
cmake_minimum_required(VERSION 3.10)
project(ffi_demo_library VERSION 0.0.1)
find_package(PkgConfig REQUIRED)
find_package(Qt5 COMPONENTS Core DBus REQUIRED)
add_library(ffi_demo SHARED
"ffi_demo.cpp"
)
target_link_libraries(ffi_demo PUBLIC Qt5::Core Qt5::DBus)
set_target_properties(ffi_demo PROPERTIES
PUBLIC_HEADER ffi_demo.h
OUTPUT_NAME "ffi_demo"
)
target_compile_definitions(ffi_demo PUBLIC DART_SHARED_LIB)
Примечание:
Следует обратить внимание, что файл реализации шаблона ffi_demo.c был переименован в ffi_demo.cpp.
В файле <project>/src/ffi_demo.h необходимо указать функцию для получения названия устройства
следующим образом:
#ifdef __cplusplus
extern "C" {
#endif
const char *getDeviceName();
#ifdef __cplusplus
}
#endif
В файле ffi_demo.cpp выполнить реализацию метода, который через DBus обратится к службе
ru.omp.deviceinfo для получения названия устройства:
#include <cstring>
#include <QtDBus/QtDBus>
#include "ffi_demo.h"
namespace {
const char* copyString(const QString& str) {
auto* tmp = new char[str.length() + 1];
std::strcpy(tmp, str.toUtf8().data());
return tmp;
}
} // пространство имён
const char* getDeviceName() {
if (QDBusConnection::systemBus().isConnected()) {
QDBusInterface iface(
"ru.omp.deviceinfo",
"/ru/omp/deviceinfo/Features",
"ru.omp.deviceinfo.Features",
QDBusConnection::systemBus()
);
if (iface.isValid()) {
QDBusReply<QString> reply = iface.call("getDeviceModel");
if (reply.isValid()) {
return copyString(reply.value());
}
}
}
return "";
}
После модификаций плагина следует обновить привязки библиотеки к языку Dart:
flutter-aurora pub run ffigen --config ffigen.yaml
Код Dart-плагина можно доработать следующим образом:
library ffi_demo;
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'ffi_demo_bindings_generated.dart';
class FFIDemo {
final FfiDemoBindings _bindings = FfiDemoBindings(
DynamicLibrary.open('libffi_demo.so'),
);
/// Реализация метода для получения имени устройства
String? getDeviceName() => using((Arena arena) {
// Выполнение метода
final Pointer<Utf8> pointer = _bindings.getDeviceName().cast<Utf8>();
// Освобождение памяти
arena.using(pointer, calloc.free);
// Возвращение результата
final deviceName = pointer.toDartString();
if (deviceName == '') {
return null;
}
return deviceName;
});
}
Для обновления зависимостей плагина следует выполнить команду в корне проекта:
flutter-aurora pub get
Доработка примера
В проекте плагина FFI, сгенерированного по шаблону plugin_ffi, в директории example имеется
приложение-пример для работы с плагином.
Его необходимо доработать для вывода информации о названии устройства.
Для получения доступа к "Device Info API" требуется добавить разрешение в файл <plugin>/example/aurora/desktop/ru.aurora.ffi_demo_example.desktop:
Permissions=DeviceInfo
Для подключения библиотеки QtDBus в файл <plugin>/example/aurora/rpm/ru.aurora.ffi_demo_example.spec
следует добавить зависимости:
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5DBus)
Для повышения читаемости кода, систематизации и упрощения написания приложений-примеров
был разработан плагин internal_aurora.
Добавить в зависимости плагин internal_aurora в pubspec.yaml можно следующим образом:
dependencies:
internal_aurora:
git:
url: https://developer.auroraos.ru/git/flutter/flutter-community-plugins/internal_aurora.git
Обновить зависимости в директории example можно следующим образом:
cd example
flutter-aurora pub get
Далее следует доработать приложение, которое будет использовать плагин ffi_demo и
демонстрировать название устройства:
import 'package:ffi_demo/ffi_demo.dart';
import 'package:flutter/material.dart';
import 'package:internal_aurora/list_item_data.dart';
import 'package:internal_aurora/list_item_info.dart';
import 'package:internal_aurora/list_separated.dart';
import 'package:internal_aurora/theme/colors.dart';
import 'package:internal_aurora/theme/theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final FFIDemo _plugin = FFIDemo();
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: internalTheme,
home: Scaffold(
appBar: AppBar(
title: const Text('FFI Demo'),
),
body: ListSeparated(
children: [
const ListItemInfo("""
An example of a platform-dependent plugin that works with the
Aurora OS via the FFI.
"""),
ListItemData(
'Get Device Name',
InternalColors.purple,
description:
'Getting the device name through a platform-dependent plugin FFI via D-Bus',
value: _plugin.getDeviceName(),
),
],
),
),
);
}
}
Теперь можно запустить приложение. В корне проекта выполнить команду Flutter CLI для сборки и запуска приложения:
flutter-aurora run
Примечание:
Для сборки доступны 3 архитектуры, более детально с этим вопросом можно ознакомиться в разделе "Целевая архитектура".
После успешно выполненной задачи по сборке будет выведен путь к файлу RPM c собранным приложением:
┌─ Result ───────────────────────────────────────────────────────────────────────────────────────────┐
│ ./build/aurora/psdk_5.0.0.60/aurora-x64/release/RPMS/ru.aurora.ffi_demo_example-0.1.0-1.x86_64.rpm │
└────────────────────────────────────────────────────────────────────────────────────────────────────┘