Дружим Flutter с С# и С++
На Flutter`е очень удобно и хорошо пишутся интерфейсы для пользователей. Но использовать Dart для решения алгоритмических задач тяжко и неэффективно. Семейство Си справляется гораздо лучше и позволяет легко распараллеливать вычисления. Кроме того, за многолетнюю историю С++ и С# обзавелись множеством полезных библиотек, не все из которых имеют аналоги во Flutter.
Зная про существование библиотеки FFI для Flutter, что позволяет даже синхронно запускать Си-шный код, я решил закопаться в эту тему и попробовать объединить наследие Си и их эффективность с удобным фреймворком. Учитывая то, что в интернете маловато информации про использование FFI, особенно с C#, я решил поделиться своим опытом «построения мостов» на примере двух приложений в этой статье.

Немного теории
FFI — это foreign function interface, или же интерфейс внешних функций. Это механизм, позволяющий программе на одном языке вызывать функции, написанные на другом. В случае с Flutter-ом, это возможность вызывать из Dart функции Си из скомпилированных библиотек. Функции, что можно вызвать из вне обязательно перед компиляцией должны быть помечены как внешние и являться статическими.
Библиотека FFI позволяет работать с внешними функциями на всех популярных платформах. Но в этой статье я проверял работу библиотеки только для Windows, однако, не думаю, что на других платформах должны быть из ряда вон выходящие осложнения.
Работа с симуляцией на С++
Моя первая курсовая работа была написана на плюсах и выводила результаты в виде bmp файлов. Когда же подошло время второй курсовой, я понял, что явно нужно сделать хоть какой-то интерфейс, отличный от проводника Windows. Переписывать тысячи строк кода такое себе удовольствие. Кроме того, Dart — всё ещё однопоточный язык, а распараллелить вычисления так же легко, как с openMP, не получится. Так что было решено создать программу по такому образу: С++ выполняет шаги симуляции, хранит в себе все данные и состояние, а Flutter поддерживает симуляцию и получает состояние симуляции, отображая его на интерфейсе.
Благо, есть документация о том, как создать ffi плагин. В ней можно легко найти команду:
flutter create —template=plugin_ffi —platforms=android,ios,linux,macos,windows ffigen_app
В созданном плагине даже сразу есть example, который можно изучить, чтобы понять, как с точки зрения кода работает вызов функции.
Однако здесь же меня ждала первая проблема — код Dart-а в этом шаблоне написан с использование ffigen. Кодогенерация всегда хорошо, но было болезненно осознавать, что она работает только для чистого Си. Так что пришлось самостоятельно записывать каждый вызов функций, а также править сгенерированный CMake файл.
С последним достаточно легко. Необходимо вписать все необходимые файлы с кодом (например, main.cpp можно не включать), указать заголовочный файл, а также подключить необходимые пакеты (например, openMP, чтобы работали его директивы). Вот пример из моего проекта.
Перед тем, как начинать работу с функциями, необходимо настроить само подключение к библиотеке. Для этого достаточно написать путь к ней и динамически открыть её с помощью DynamicLibrary.open(pathToLib/libName.dll) . Именно из этого объекта с помощью метода lookup мы будем вызывать функции при помощи их названия на стороне С++.
Но и этого недостаточно, ибо только простые типы данных можно передавать между языками. То есть аргументы функции и возвращаемый тип в обоих языках обязан быть простым. Для передачи структурированных данных, необходимо воспользоваться указателями и структурами. В Dart необходимо создать класс, наследованный от Struct , а также делать его аналог в коде С++, после чего работать с этим классом через Dart класс Pointer . Причём, обязательно нужно сохранять порядок полей структур в обоих языках:
Пример структуры в Dart:
final class IntArray extends Struct < external Pointerdata; @Int32() external int length; >
Пример этой же структуры в С++:
typedef struct < int *data; int length; >IntArray;
Здесь показан пример создания массива. Другие структуры создаются аналогично. Как пример, структура модели локации, что используется в симуляции. Так как я не хотел бы в бизнес-логике приложения работать с классом Pointer, то для каждой структуры пишем класс без указателей.
Стоит упомянуть, что указатель в Dart создаётся намного более проблематично, нежели в С++. Для создания необходимо подключить уже не только библиотеку dart:ffi , но и пакет package:ffi/ffi.dart , для того, чтобы воспользоваться классом Arena , с помощью которого можно выделять память:
- Создаём объект для выделения памяти
Arena arena = Arena(); - Создаём нужные указатели, например, таким образом:
Pointer townsPriority = arena.allocate(length * sizeInt); - После того, как созданные указатели перестали быть нам нужны, очищаем память:
arena.releaseAll();
Теперь, когда есть все необходимые структуры данных и подключение к библиотеке, настало время писать глобальные внешние функции и функции, что вызывают внешние функции.
На стороне С++ я создал отдельный файлик FlutterAdapter.cpp, который хранит всё для работы с внешними функциями. Из необычного в нём только FFI_PLUGIN_EXPORT перед теми функциями, которые являются внешними. Аналогично FFI_PLUGIN_EXPORT нужно написать и в заголовочном файле перед объявлением функций. Кроме того, в нём ещё необходимо сделать два действия. Во-первых, добавить в начало:
#if _WIN32 #define FFI_PLUGIN_EXPORT __declspec(dllexport) #else #define FFI_PLUGIN_EXPORT #endif
А во-вторых, обернуть все объявления в extern «C» <> . Это ключевое слово необходимо для сохранности кода при переводе С++ и Си. Однако, его добавление может вызвать проблемы из-за того, например, отсутствия поддержки template у функций.
Согласен с мыслью, что легче один раз увидеть, чем пытаться прочитать теорию, так что для стороны Dart-а я просто покажу примеры: вот пример кода с получением указателя, а вот пример кода с передачей аргументов в С++.
Но и это ещё не всё! Так как Dart однопоточный, то при вызове длительной функции на стороне С++ интерфейс на Flutter-е «прикажет долго жить» и зависнет. Для того, чтобы этого не происходило, необходимо настроить работу с отдельным изолятом. Это достаточно много сложного кода, который необходимо было повторять для каждой такой функции (причём эту работу должен был делать ffigen), но у меня получилось при помощи парочки костылей настроить вызов изолята для любой функции. Подобные вызовы функции выглядят таким образом:
Future makeStep(int stepCount) async < return await executeInIsolate( _makeStepForIsolate, ); > void _makeStepForIsolate(Map args) < final execute = lookup>('execute') .asFunction(); return execute(args['stepCount']); >
Всё выглядит так костыльно из-за того, что в SendPort можно передавать только верхнеуровневые функции. Для этого отдельно и объявляется _makeStepForIsolate. Аргументы записываются в Map для того, чтобы сохранить возможность передать абсолютно любую функцию внутрь executeInIsolate . Ждём макросы в Dart.
Для тех, кто хочет разобраться, каким же образом создаётся изолят и выполняются функции в отдельном потоке, вот в этом файле я оставил достаточно много комментариев.
Перед подведением итогов хочу ещё сказать о том, что было достаточно проблематично писать этот проект из-за проблем с дебагом. Писал код Flutter-а в VScode и иногда через него же делал правки в код плюсов. Во-первых, при компиляции и запуске программы ошибки C++ выводятся в debug console, но на другой кодировке, из-за чего большинство ошибок мне приходилось гуглить по её номеру, ибо текст ошибки представлял собой кучу вопросов. Скорее всего это из-за русского языка. Настройки CMake в vs-code не помогли. Во-вторых, я заметил проблему, что при создании функции с 20-ю и более аргументами программа завершается без вывода ошибки.
Подведём итог. Для построения мостов между языками необходимо: настроить CMake, на стороне С++ пометить внешние функции и скомпилировать код в dll, на Dart подключиться к этой библиотеке и вызвать функцию по названию, при необходимости создать на двух языках одинаковые структуры и передавать указатели на них, и при тяжеловесности функции создаём отдельный изолят для вызова.
Документооборот на C#
Была задача для десктопа: оптимизировать заполнение определённых типов документов, чтобы их можно было делать не через Office, а через удобную специальную программу. Конечно же, я сразу вспомнил C#, ибо пару раз подобное уже писал на нём. Но желание экспериментировать с FFI заставило меня пересмотреть мой выбор в сторону сразу двух языков. Да и, кроме того, на момент создания программы для Flutter не было нормальной библиотеки для работы с Word, а заказчик хотел иметь возможность редактировать и Word и Excel.
Настрадавшись с прошлым проектом, было решено в этом минимизировать точки соприкосновения двух языков. Как следствие этого — программа имеет лишь две внешние функции, аргументы которой являются json-ами. При вызове метода makeFile в одном большом json передаётся информация о всех необходимых структурах. Очень удобно, хоть, конечно, нужно понимать, что это чуточку мешает производительности. Кроме того, чтобы не было проблем с форматом обязательно нужно воспользоваться json.encode на стороне Dart-а и не запутаться в кодировках. Напомню, что Utf8 не поддерживает кириллицу. Так что подготовка аргументов на стороне Dart выглядит вот так:
final json = json.encode(data.toJson()); final pointer = json.toNativeUtf16(); final result = dartFunc(pointer);
Так что на сей раз абсолютно вся работа с FFI на стороне Dart поместилась в один файлик.
Если в прошлый раз сразу же создавался простой CMake файл, в котором нужно было заполнить парочку строчек, чтобы всё работало по нажатию одной кнопки, то на сей раз у меня была только минимальная информация из интернета. К сожалению, мои поиски ни к чему не привели. Однако, это и не особо нужно, ведь всю работу по компиляции можно выполнить при помощи обычного скрипта.
Касательно создания самого dll файла. .NET Core поддерживает AOT компиляцию, как и Dart, но только с помощью определённого компилятора и парочки команд dotnet. Все шаги были проделаны на .net 6.0. Не могу точно сказать, работают ли команды на более высокой версии. Буду рад, если в комментариях кто-нибудь напишет, получилось ли на более высокой.
Для начала я создал обычное консольное приложение C# через VS. Далее через консоль добавляем необходимый компилятор командой:
dotnet add package Microsoft.DotNet.ILCompiler -v 7.0.0-*
Теперь проект готов к тому, чтобы его наполнили кодом и библиотеками. В этой программе для работы с excel использовал epplus, что подключил через nuget. Внешние функции также выделяем в отдельный файлик, делаем статическими и помечаем атрибутом, который хранит в себе имя, которое мы будем искать через lookup :
Пользуемся Marshal.PtrToStringUni , помня о кодировке и используя встроенную десериализацию, чтобы получить готовые к работе модели. Как раз из-за json формата передачи появилась проблема с неймингом полей передаваемых моделей, так как стили в Dart и C# разные. Я решил, что пусть уж C# потерпит это нарушение, так что названия полей сделал точно как в Dart, то есть с маленькой буквы:
public string name
Тестируем программу в режиме консольного приложения, но для компиляции обязательно меняем тип проекта на библиотеку классов. Чтобы получить dll файл билдим проект и пишем в консоль:
dotnet publish /p:NativeLib=Shared /p:SelfContained=true -r win-x64 -c release(или debug)
Спустя достаточно долгое время работы компилятора в папке bin/Release/net.6.0/win-x64/publish появился нужны dll файл. Внутри него сразу включены все нужные ему библиотеки, так что достаточно воспользоваться только им.
Подведём итог. На сей раз достаточно лишь: создать проект и подключить компилятор, пометить внешние функции, скомпилить dll и перенести в нужное место, в Dart подключиться к библиотеке и передать в вызов функции json.
Заключение
По итогу у меня получились два приложения, с разными алгоритмическими языками и с разными подходами к построению связи с Flutter-ом. В целом, полученных на практике знаний вполне достаточно для того, чтобы без страха в следующий раз подключать код семейста Си в проект.
Плюсами такого подхода считаю увеличение производительности и расширение пула библиотек, ибо теперь помимо pub у нас есть все библиотеки С++ и C#.
К минусам я могу отнести повышенный вес приложения, увеличение времени компиляции проекта и сложность отладки.
Flutter : the code execution cannot proceed because flutter_windows.dll was not found. Reinstalling the program may fix this problem

Hello I am trying to create desktop app from flutter and I genrate exe file and .dll file also. When I install in my PC its perfectly work but when I share to other and they trying to install then its fire error «flutter exe file error the code execution cannot proceed because flutter_windows.dll was not found. Reinstalling the program may fix this problem»
Как исправить раздражающие ошибки, связанные с Flutter.exe [РЕШЕНО]
Основные причины ошибок исполняемого файла EXE связаны с отсутствием или повреждением файла flutter.exe или, в некоторых случаях, заражением вредоносным ПО. Как правило, эти ошибки наблюдаются во время запуска Flutter. Как правило, любую проблему, связанную с файлом EXE, можно решить посредством замены файла на новую копию. Кроме того, некоторые ошибки flutter.exe могут возникать по причине наличия неправильных ссылок на реестр. По этой причине для очистки недействительных записей рекомендуется выполнить сканирование реестра.
Windows Executable File форматы классифицируют в качестве Исполнимые файлы, и они имеют расширение EXE. Ниже представлена наша база версий файлов flutter.exe для большинства выпусков операционной системы Windows (включая %%os%%), где вы также можете их скачать. В нашей базе представлены не все версии flutter.exe, поэтому нажмите на кнопку Request (Запрос), чтобы наши сотрудники её получили. Кроме того, Bot Square, Inc. также может предоставить вам некоторые редкие файлы, которые отсутствуют в нашем каталоге.
Правильное расположение файла flutter.exe является решающим фактором в успешном устранении ошибок подобного рода. Однако, не будет лишним выполнить быструю проверку. Повторно запустите Flutter, чтобы убедиться, что проблема успешно решена.
| flutter.exe Описание файла | |
|---|---|
| Расширение: | EXE |
| Функция: | Flutter |
| App: | Flutter |
| Версия: | 0.4.8.0 |
| Компания: | Bot Square, Inc. |
| File: | flutter.exe 5f42e5c3e1aa5b1961ccf0b056682c9dbb869a27 |
| MD5: | 561d9c63c98ff878921214669718dcd8 |
| CRC32: |
FLUTTER_WINDOWS.DLL
Скачайте файл flutter_windows.dll, чтобы решить вашу проблему с dll. Сейчас у нас доступна 1 версия этого файла.
Если у вас есть другие версии этого файла, пожалуйста, поделитесь ими с сообществом, загрузив dll-файл на сайт.
flutter_windows.dll, Описание файла: —
Ошибки, связанные с flutter_windows.dll могут возникнуть по нескольким причинам. Например, ошибочная заявка: flutter_windows.dll было удалено или потеряно, искажено вредоносными программами, присутствующими в вашем компьютере; или поврежденная система Windows.
Наиболее часто встречающиеся сообщения об ошибках:
- Невозможен запуск программы, так как flutter_windows.dll не найдено в вашем компьютере. Попытайтесь повторно установить программу для устранения этой проблемы.
- Проблема запуска flutter_windows.dll. Указанный модуль не был найден
- Ошибка загрузки flutter_windows.dll. Указанный модуль не был найден.
- flutter_windows.dll либо не предназначен для работы в Windows, либо содержит ошибку.
В большинстве случаев решить проблему можно путем грамотной переустановки в системной папке Windows flutter_windows.dll в вашем компьютере. С другой стороны, для некоторых программ, в частности компьютерных игр, требуется, чтобы DLL файл был помещен в папку установки игры/приложения.
Для подробной инструкции по установке смотрите наши ЧАВО.