Можно ли создать приложение на C++ и использовать Flutter в качестве среды графического интерфейса?

Я создал приложение на C++, которое работает на встроенном Linux (OrangePi), и в настоящее время оно использует экран HMI (Nextion). Но это действительно привязывает меня к конкретной марке экрана и функциям, которые они предоставляют.

Недавно я создал другое приложение с полным графическим интерфейсом, используя исключительно Flutter, и оно мне очень нравится. Это также намного приятнее, чем писать графический интерфейс с нуля с использованием любой библиотеки C++, и имеет преимущество кроссплатформенности.

Мне было интересно, возможно ли/разумно отказаться от экрана HMI для приложения C++ и использовать Flutter? Это позволит мне, вероятно, запустить его и на мобильных устройствах в будущем, что будет бонусом.

Идея высокого уровня заключалась бы в том, чтобы запустить Flutter в качестве основного «потока», а затем загрузить мое приложение C++ в качестве общей библиотеки и запустить его во втором «потоке»/изолировать. Затем, когда нажимаются кнопки в графическом интерфейсе, dart будет вызывать C, а когда новые данные будут отображены на экране, C будет вызывать dart (это направление кажется более неудобным).

Судя по тому, что я вижу в Интернете, dart-ffi — это инструмент, который можно использовать для этого. Кажется, что он может «легко» звонить из dart в C, но не наоборот, если только не используются обратные вызовы. Я не думаю, что это действительно сработает для меня, поскольку приложению C++ независимо придется часто вызывать dart для обновления графического интерфейса без взаимодействия с пользователем, что исключает использование обратного вызова в традиционном смысле. У меня очень ограниченный опыт работы с JNI для выполнения подобных задач, и у него нет проблем с вызовом с C на Java/Kotlin, так что, надеюсь, я просто что-то упускаю в dart-ffi.

NativeCallable.listener выглядит интересно, но каждый раз закрывает обратный вызов после его вызова. На самом деле я не хочу этого делать, я бы хотел, чтобы C++ вызывал вызов, когда захочет. Судя по тому, что я там читаю, кажется, что каждый раз, когда вы создаете обратный вызов, создается новый изолят. Если бы мне пришлось создавать множество обратных вызовов при запуске и передавать их приложению C++, чтобы оно могло использовать их как обычные вызовы функций на протяжении всей своей жизни, все эти изоляты были бы проблемой.

Есть ли способ заставить Flutter работать в моем случае? Мне не хватает некоторых примеров или документации о том, как заставить это работать?

«Это также намного приятнее, чем писать графический интерфейс с нуля с помощью любой библиотеки C++…» Я полностью согласен. Предоставленный Flutter графический интерфейс намного лучше. Я использовал wxWidgets и Qt в C++, а также Flutter для создания графического интерфейса. Флаттер лучше по количеству усилий, а также по внешнему виду.

user12002570 22.07.2024 05:33

@user12002570 user12002570 вы использовали Flutter с серверной частью C++ или просто отдельно?

av4625 22.07.2024 21:52

Я думал об использовании C++ в качестве серверной части и Flutter в качестве внешнего интерфейса, но потом понял, что смешивать C++ с любым другим языком в проекте слишком сложно. C++ только усложнит ситуацию. Затем я использовал Flutter для всего проекта (бэкэнд и интерфейс). Несколько лет назад я также задал очень похожий вопрос о самой SO.

user12002570 23.07.2024 05:28

Почему бы не запустить бит C++ в отдельном процессе и не поговорить с ним через сокет?

Richard Heap 27.07.2024 02:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я нашел способ объединить серверную часть C++ и интерфейс Flutter. Это означает, что вам нужно передавать любые функции, которые вы хотите, чтобы C++ вызывал асинхронно, в качестве обратных вызовов в начале, что не идеально, но выполнимо в качестве шага инициализации.

Обратите внимание, что это все еще находится в состоянии POC с глобальными переменными и т. д.

С++

#include <chrono>
#include <functional>
#include <thread>

#if defined(_WIN32)
#define DART_EXPORT extern "C" __declspec(dllexport)
#else
#define DART_EXPORT                                                            \
  extern "C" __attribute__((visibility("default"))) __attribute((used))
#endif

std::function<void(int)> update;
std::function<void(int)> ping;

DART_EXPORT void start_app()
{
    for (int i = 0; i < 5; ++i)
    {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        update(1);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        ping(5);
    }
}

DART_EXPORT void set_update(void (*update_dart)(int))
{
    update = update_dart;
}

DART_EXPORT void set_ping(void (*ping_dart)(int))
{
    ping = ping_dart;
}

Дарт

import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';

import 'dylib_utils.dart';

typedef Callback = Void Function(Int);

typedef SetFunction = void Function(Pointer<NativeFunction<Callback>>);
typedef SetNativeFunction = Void Function(Pointer<NativeFunction<Callback>>);

late final DynamicLibrary dylib = dlopenPlatformSpecific(
  "backend",
  paths: [
    Platform.script.resolve('../lib/'),
    Uri.file(Platform.resolvedExecutable),
  ]
);

final nativeSetUpdate = dylib.lookupFunction<SetNativeFunction, SetFunction>(
  "set_update"
);
final nativeSetPing = dylib.lookupFunction<SetNativeFunction, SetFunction>(
  "set_ping"
);

typedef StartAppFunction = void Function();
typedef StartAppNativeFunction = Void Function();
final nativeStartApp = dylib
  .lookupFunction<StartAppNativeFunction, StartAppFunction>("start_app");

void app(final String message) {
  nativeStartApp();
}

void setUpdate() {
  void onNativeUpdate(final int amount) {
    print("Got an update $amount");
  }

  final callback = NativeCallable<Callback>.listener(onNativeUpdate);
  nativeSetUpdate(callback.nativeFunction);
  callback.keepIsolateAlive = false;
}

void setPing() {
  void onNativePing(final int amount) {
    print("Got a ping $amount");
  }

  final callback = NativeCallable<Callback>.listener(onNativePing);
  nativeSetPing(callback.nativeFunction);
  callback.keepIsolateAlive = false;
}

Future<void> main() async {
  print("Setting update...");
  setUpdate();

  print("Setting ping...");
  setPing();

  print("Starting app...");
  final ReceivePort exitListener = ReceivePort();
  Isolate.spawn(app, "test", onExit: exitListener.sendPort);
  exitListener.listen((message){
    if (message == null) { // A null message means the isolate exited
      print("App stopped...");
      exitListener.close();
    }
  });
}

Реализация dlopenPlatformSpecific находится здесь.

Другие вопросы по теме