#c #flutter #dart #ffi #flutter-desktop
#c #flutter #dart #ffi #flutter-рабочий стол
Вопрос:
У меня есть несколько библиотек для взаимодействия с чипом FTDI, которые я упаковал в DLL на C . Я хотел бы создать интерфейс с помощью Flutter и использовать эту библиотеку в настольном приложении Windows. Эти функции все еще являются новыми во Flutter, а документация очень поверхностна и специфична для мобильных устройств.
Следуя приведенному здесь руководству, я создал плагин с помощью FFI:
import 'dart:ffi';
import 'dart:io';
import 'dart:async';
import 'package:flutter/services.dart';
final DynamicLibrary FT232H = DynamicLibrary.open("");
final int Function() initializeLibrary = FT232H
.lookup<NativeFunction<Uint8 Function()>>("initialize_library")
.asFunction();
final void Function() cleanupLibrary = FT232H
.lookup<NativeFunction<Void Function()>>("cleanup_library")
.asFunction();
final int Function() initializeI2C = FT232H
.lookup<NativeFunction<Uint8 Function()>>("Initialize_I2C")
.asFunction();
final int Function() closeI2C = FT232H
.lookup<NativeFunction<Uint8 Function()>>("Close_I2C")
.asFunction();
final int Function(
Uint8 slaveAddress, Uint8 registerAddress, Uint32 data, Uint32 numBytes)
i2cWriteBytes = FT232H
.lookup<NativeFunction<Uint8 Function(Uint8, Uint8, Uint32, Uint32)>>(
"I2C_write_bytes")
.asFunction();
final int Function(Uint8 slaveAddress, Uint8 registerAddress,
Uint8 bRegisterAddress, Pointer<Uint8> data, Uint32 numBytes)
i2cReadBytes = FT232H
.lookup<
NativeFunction<
Uint8 Function(Uint8, Uint8, Uint8, Pointer<Uint8>,
Uint32)>>("I2C_read_bytes")
.asFunction();
class DllImport {
static const MethodChannel _channel = const MethodChannel('dll_import');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
А вот мой заголовочный файл на другой стороне:
#pragma once
/* Include D2XX header*/
#include "ftd2xx.h"
/* Include libMPSSE headers */
#include "libMPSSE_i2c.h"
#include "libMPSSE_spi.h"
#define FT232H_EXPORTS
#ifdef FT232H_EXPORTS
#define FT232H_API __declspec(dllexport)
#else
#define FT232H_API __declspec(dllimport)
#endif
extern "C" FT232H_API uint8 initialize_library();
extern "C" FT232H_API void cleanup_library();
extern "C" FT232H_API FT_STATUS Initialize_I2C();
extern "C" FT232H_API FT_STATUS Close_I2C();
extern "C" FT232H_API FT_STATUS I2C_write_bytes(uint8 slaveAddress, uint8 registerAddress,
const uint8 * data, uint32 numBytes);
extern "C" FT232H_API FT_STATUS I2C_read_bytes(uint8 slaveAddress, uint8 registerAddress,
uint8 bRegisterAddress, uint8 * data, uint32 numBytes);
Здесь у меня возникают некоторые проблемы с указателями Uint8, похоже, я получаю эту ошибку из своего кода Dart :
The type 'Uint8 Function(Uint8, Uint8, Uint8, Pointer<Uint8>, Uint32)' must be a subtype of 'int
Function(Uint8, Uint8, Uint8, Pointer<Uint8>, Uint32)' for 'asFunction'.
Try changing one or both of the type arguments.dart(must_be_a_subtype)
Любые указания о том, как это сделать в flutter, будут с благодарностью приняты!
Спасибо.
Комментарии:
1. Может отсутствовать имя файла в
DynamicLibrary.open("");
?2. Многие из ваших типов функций объявлены в вашем коде Dart как возвращающие an
int
, но, похоже, вместо этого они должны возвращать aUint8
. Это сработает, если вы это исправите?3. Тип Uint8 не существует в dart, я должен привязать свои функции к типам данных dart или я получаю сообщение об ошибке. Кроме того, DynamicLibrary.open («»); был заполнен, я ищу больше руководства о том, как это сделать в целом, поскольку здесь работает много разных частей flutter, которые находятся в бета-версии и на данный момент плохо документированы.
4. Вероятно, я мог бы написать для вас официальное руководство, когда позже получу доступ к своему компьютеру с Windows. На данный момент, хотя вместо чтения
uint8* data
как aPointer<Uint8>
, вы можете прочитать его как uint64? поскольку это должен быть размер вашего указателя в зависимости от используемой вами архитектуры (при условии 64-битной версии), затем преобразуйте Uint64 в указатель <Uint8>?5. @Nina Это было бы очень полезно и очень полезно для всех, кто работает с настольными приложениями Windows с помощью flutter, поскольку использование dll является важной частью любых настольных приложений Windows. На данный момент даже с правильными типами данных я не уверен, смогу ли я закрыть цикл и заставить его работать с имеющейся у меня информацией (:
Ответ №1:
У меня есть решение, и оно работает с кодом barebone, предоставленным в проекте Flutter-Desktop-Embedding, который, я полагаю, вы использовали для своего настольного приложения. Вы на правильном пути, но просто нуждаетесь в некоторой доработке.
Для тестирования я использовал этот простой код на c с несколькими функциями для проверки передачи указателей, возврата указателей, заполнения памяти, выделения и освобождения. Это код C, который я использовал в своей dll.
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
__declspec(dllexport) uint8_t* createarray(int32_t size) {
uint8_t* arr = malloc(size);
return arr;
}
__declspec(dllexport) void populatearray(uint8_t* arr,uint32_t size){
for (uint32_t index = 0; index < size; index) {
arr[index] = index amp; 0xff;
}
}
__declspec(dllexport) void destroyarray(uint8_t* arr) {
free(arr);
}
createarray
выделяет указатель uint8_t с заданным размером и возвращает его вызывающему.
populatearray
принимает аргумент указателя uint8_t вместе с размером и заполняет его индексом
destroyarray
просто освобождает выделенную память.
Теперь для стандартного кода flutter.
Это код по умолчанию, предусмотренный main.dart
в проекте Flutter-Desktop-Embedding, который я клонировал отсюда https://github.com/google/flutter-desktop-embedding.git
(я предполагаю, что вы уже выполнили этот шаг)
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:io' show Platform;
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:menubar/menubar.dart';
import 'package:window_size/window_size.dart' as window_size;
import 'keyboard_test_page.dart';
void main() {
// Try to resize and reposition the window to be half the width and height
// of its screen, centered horizontally and shifted up from center.
WidgetsFlutterBinding.ensureInitialized();
window_size.getWindowInfo().then((window) {
final screen = window.screen;
if (screen != null) {
final screenFrame = screen.visibleFrame;
final width = math.max((screenFrame.width / 2).roundToDouble(), 800.0);
final height = math.max((screenFrame.height / 2).roundToDouble(), 600.0);
final left = ((screenFrame.width - width) / 2).roundToDouble();
final top = ((screenFrame.height - height) / 3).roundToDouble();
final frame = Rect.fromLTWH(left, top, width, height);
window_size.setWindowFrame(frame);
window_size.setWindowMinSize(Size(0.8 * width, 0.8 * height));
window_size.setWindowMaxSize(Size(1.5 * width, 1.5 * height));
window_size
.setWindowTitle('Flutter Testbed on ${Platform.operatingSystem}');
}
});
runApp(new MyApp());
}
/// Top level widget for the application.
class MyApp extends StatefulWidget {
/// Constructs a new app with the given [key].
const MyApp({Key? key}) : super(key: key);
@override
_AppState createState() => new _AppState();
}
class _AppState extends State<MyApp> {
Color _primaryColor = Colors.blue;
int _counter = 0;
static _AppState? of(BuildContext context) =>
context.findAncestorStateOfType<_AppState>();
/// Sets the primary color of the app.
void setPrimaryColor(Color color) {
setState(() {
_primaryColor = color;
});
}
void incrementCounter() {
_setCounter(_counter 1);
}
void _decrementCounter() {
_setCounter(_counter - 1);
}
void _setCounter(int value) {
setState(() {
_counter = value;
});
}
/// Rebuilds the native menu bar based on the current state.
void updateMenubar() {
setApplicationMenu([
Submenu(label: 'Color', children: [
MenuItem(
label: 'Reset',
enabled: _primaryColor != Colors.blue,
shortcut: LogicalKeySet(
LogicalKeyboardKey.meta, LogicalKeyboardKey.backspace),
onClicked: () {
setPrimaryColor(Colors.blue);
}),
MenuDivider(),
Submenu(label: 'Presets', children: [
MenuItem(
label: 'Red',
enabled: _primaryColor != Colors.red,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.shift, LogicalKeyboardKey.keyR),
onClicked: () {
setPrimaryColor(Colors.red);
}),
MenuItem(
label: 'Green',
enabled: _primaryColor != Colors.green,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.alt, LogicalKeyboardKey.keyG),
onClicked: () {
setPrimaryColor(Colors.green);
}),
MenuItem(
label: 'Purple',
enabled: _primaryColor != Colors.deepPurple,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.control, LogicalKeyboardKey.keyP),
onClicked: () {
setPrimaryColor(Colors.deepPurple);
}),
])
]),
Submenu(label: 'Counter', children: [
MenuItem(
label: 'Reset',
enabled: _counter != 0,
shortcut: LogicalKeySet(
LogicalKeyboardKey.meta, LogicalKeyboardKey.digit0),
onClicked: () {
_setCounter(0);
}),
MenuDivider(),
MenuItem(
label: 'Increment',
shortcut: LogicalKeySet(LogicalKeyboardKey.f2),
onClicked: incrementCounter),
MenuItem(
label: 'Decrement',
enabled: _counter > 0,
shortcut: LogicalKeySet(LogicalKeyboardKey.f1),
onClicked: _decrementCounter),
]),
]);
}
@override
Widget build(BuildContext context) {
// Any time the state changes, the menu needs to be rebuilt.
updateMenubar();
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: _primaryColor,
accentColor: _primaryColor,
),
darkTheme: ThemeData.dark(),
home: _MyHomePage(title: 'Flutter Demo Home Page', counter: _counter),
);
}
}
class _MyHomePage extends StatelessWidget {
const _MyHomePage({required this.title, this.counter = 0});
final String title;
final int counter;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: viewportConstraints.maxHeight),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
new Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
TextInputTestWidget(),
new ElevatedButton(
child: new Text('Test raw keyboard events'),
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (context) => KeyboardTestPage()));
},
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 380.0,
height: 100.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1.0)),
child: Scrollbar(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
itemExtent: 20.0,
itemCount: 50,
itemBuilder: (context, index) {
return Text('entry $index');
},
),
),
),
),
],
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _AppState.of(context)!.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
/// A widget containing controls to test text input.
class TextInputTestWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
SampleTextField(),
SampleTextField(),
],
);
}
}
/// A text field with styling suitable for including in a TextInputTestWidget.
class SampleTextField extends StatelessWidget {
/// Creates a new sample text field.
const SampleTextField();
@override
Widget build(BuildContext context) {
return Container(
width: 200.0,
padding: const EdgeInsets.all(10.0),
child: TextField(
decoration: InputDecoration(border: OutlineInputBorder()),
),
);
}
}
Теперь для нашей части кода мы должны создать указатель на функцию для каждой функции, которую мы хотим вызвать в dll, нам нужно правильное имя функции и корректное / правильное количество аргументов.
Нам понадобятся пакеты dart: io и dart: ffi. dart: io уже есть в коде, просто нужно импортировать dart: ffi import 'dart:ffi'; // For FFI
Теперь, чтобы создать дескриптор библиотеки, которую необходимо загрузить, необходимо вызвать DynamicLibrary.open с именем библиотеки dll. (DLL необходимо поместить в путь выполнения приложения dart или указать абсолютный путь. Путь выполнения — build / windows / runner / Debug)
final DynamicLibrary nativePointerTestLib = DynamicLibrary.open("dynamicloadtest.dll");
Вызывается мой дескриптор nativePointerTestLib
, а имя библиотеки dll «dynamicloadtest.dll » (Да, я, вероятно, должен использовать лучшие соглашения об именах)
Далее необходимо создать указатель на каждую функцию. В dll есть три функции, которые я хочу вызвать: createarray, populatearray, destroyarray.
Первый принимает аргумент размера int -> возвращает указатель на массив (указатель) Второй принимает указатель вместе с size -> void return, третий принимает только указатель -> void return
final Pointer<Uint8> Function(int size) nativeCreateArray =
nativePointerTestLib
.lookup<NativeFunction<Pointer<Uint8> Function(Int32)>>("createarray")
.asFunction();
final void Function(Pointer<Uint8> arr,int size) nativePopulateArray =
nativePointerTestLib
.lookup<NativeFunction<Void Function(Pointer<Uint8>, Int32)>>("populatearray")
.asFunction();
final void Function(Pointer<Uint8> arr) nativeDestroyArray =
nativePointerTestLib
.lookup<NativeFunction<Void Function(Pointer<Uint8>)>>("destroyarray")
.asFunction();
Я назвал указатели на функции nativeCreateArray
, nativePopulateArray
, nativeDestroyArray
Наконец, это просто вопрос вызова каждой функции и тестирования, чтобы убедиться, что они работают. Я просто выбрал случайную функцию в коде boiler plate, void _setCounter(int value)
которая устанавливает значение счетчика и позже отображается. Я просто собираюсь добавить дополнительный код к этому методу для выполнения наших вызовов функций, а также распечатать результаты, чтобы увидеть, сработало ли это. старый метод
void _setCounter(int value) {
setState(() {
_counter = value;
});
}
новый метод с нашими вызовами функций
void _setCounter(int value) {
setState(() {
Pointer<Uint8> parray = nativeCreateArray(5);
nativePopulateArray(parray,5);
//Now lets print
print(parray);
String str= "";
for(int i = 0 ; i < 5; i){
int val = parray.elementAt(i).value;
str =val.toString() " ";
}
print(str);
nativeDestroyArray(parray);
_counter = value;
});
}
Я вызвал nativeCreate с размером 5. Библиотека dll выделит 5 байт для массива.
Затем я вызываю populate, который вставит индекс от 0 до 4 в каждый элемент массива.
Затем я перебираю массив, захватывая каждый элемент в этом индексе массива, а затем получаю значение. Я присваиваю это значение строке и, наконец, печатаю и уничтожаю массив.
Окончательный код все вместе:
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'dart:io' show Platform;
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:menubar/menubar.dart';
import 'package:window_size/window_size.dart' as window_size;
import 'keyboard_test_page.dart';
import 'dart:ffi'; // For FFI
final DynamicLibrary nativePointerTestLib = DynamicLibrary.open("dynamicloadtest.dll");
final Pointer<Uint8> Function(int size) nativeCreateArray =
nativePointerTestLib
.lookup<NativeFunction<Pointer<Uint8> Function(Int32)>>("createarray")
.asFunction();
final void Function(Pointer<Uint8> arr,int size) nativePopulateArray =
nativePointerTestLib
.lookup<NativeFunction<Void Function(Pointer<Uint8>, Int32)>>("populatearray")
.asFunction();
final void Function(Pointer<Uint8> arr) nativeDestroyArray =
nativePointerTestLib
.lookup<NativeFunction<Void Function(Pointer<Uint8>)>>("destroyarray")
.asFunction();
void main() {
// Try to resize and reposition the window to be half the width and height
// of its screen, centered horizontally and shifted up from center.
WidgetsFlutterBinding.ensureInitialized();
window_size.getWindowInfo().then((window) {
final screen = window.screen;
if (screen != null) {
final screenFrame = screen.visibleFrame;
final width = math.max((screenFrame.width / 2).roundToDouble(), 800.0);
final height = math.max((screenFrame.height / 2).roundToDouble(), 600.0);
final left = ((screenFrame.width - width) / 2).roundToDouble();
final top = ((screenFrame.height - height) / 3).roundToDouble();
final frame = Rect.fromLTWH(left, top, width, height);
window_size.setWindowFrame(frame);
window_size.setWindowMinSize(Size(0.8 * width, 0.8 * height));
window_size.setWindowMaxSize(Size(1.5 * width, 1.5 * height));
window_size
.setWindowTitle('Flutter Testbed on ${Platform.operatingSystem}');
}
});
runApp(new MyApp());
}
/// Top level widget for the application.
class MyApp extends StatefulWidget {
/// Constructs a new app with the given [key].
const MyApp({Key? key}) : super(key: key);
@override
_AppState createState() => new _AppState();
}
class _AppState extends State<MyApp> {
Color _primaryColor = Colors.blue;
int _counter = 0;
static _AppState? of(BuildContext context) =>
context.findAncestorStateOfType<_AppState>();
/// Sets the primary color of the app.
void setPrimaryColor(Color color) {
setState(() {
_primaryColor = color;
});
}
void incrementCounter() {
_setCounter(_counter 1);
}
void _decrementCounter() {
_setCounter(_counter - 1);
}
void _setCounter(int value) {
setState(() {
Pointer<Uint8> parray = nativeCreateArray(5);
nativePopulateArray(parray,5);
//Now lets print
print(parray);
String str= "";
for(int i = 0 ; i < 5; i){
int val = parray.elementAt(i).value;
str =val.toString() " ";
}
print(str);
nativeDestroyArray(parray);
_counter = value;
});
}
/// Rebuilds the native menu bar based on the current state.
void updateMenubar() {
setApplicationMenu([
Submenu(label: 'Color', children: [
MenuItem(
label: 'Reset',
enabled: _primaryColor != Colors.blue,
shortcut: LogicalKeySet(
LogicalKeyboardKey.meta, LogicalKeyboardKey.backspace),
onClicked: () {
setPrimaryColor(Colors.blue);
}),
MenuDivider(),
Submenu(label: 'Presets', children: [
MenuItem(
label: 'Red',
enabled: _primaryColor != Colors.red,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.shift, LogicalKeyboardKey.keyR),
onClicked: () {
setPrimaryColor(Colors.red);
}),
MenuItem(
label: 'Green',
enabled: _primaryColor != Colors.green,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.alt, LogicalKeyboardKey.keyG),
onClicked: () {
setPrimaryColor(Colors.green);
}),
MenuItem(
label: 'Purple',
enabled: _primaryColor != Colors.deepPurple,
shortcut: LogicalKeySet(LogicalKeyboardKey.meta,
LogicalKeyboardKey.control, LogicalKeyboardKey.keyP),
onClicked: () {
setPrimaryColor(Colors.deepPurple);
}),
])
]),
Submenu(label: 'Counter', children: [
MenuItem(
label: 'Reset',
enabled: _counter != 0,
shortcut: LogicalKeySet(
LogicalKeyboardKey.meta, LogicalKeyboardKey.digit0),
onClicked: () {
_setCounter(0);
}),
MenuDivider(),
MenuItem(
label: 'Increment',
shortcut: LogicalKeySet(LogicalKeyboardKey.f2),
onClicked: incrementCounter),
MenuItem(
label: 'Decrement',
enabled: _counter > 0,
shortcut: LogicalKeySet(LogicalKeyboardKey.f1),
onClicked: _decrementCounter),
]),
]);
}
@override
Widget build(BuildContext context) {
// Any time the state changes, the menu needs to be rebuilt.
updateMenubar();
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: _primaryColor,
accentColor: _primaryColor,
),
darkTheme: ThemeData.dark(),
home: _MyHomePage(title: 'Flutter Demo Home Page', counter: _counter),
);
}
}
class _MyHomePage extends StatelessWidget {
const _MyHomePage({required this.title, this.counter = 0});
final String title;
final int counter;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: viewportConstraints.maxHeight),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
new Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
TextInputTestWidget(),
new ElevatedButton(
child: new Text('Test raw keyboard events'),
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (context) => KeyboardTestPage()));
},
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 380.0,
height: 100.0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1.0)),
child: Scrollbar(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
itemExtent: 20.0,
itemCount: 50,
itemBuilder: (context, index) {
return Text('entry $index');
},
),
),
),
),
],
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _AppState.of(context)!.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
/// A widget containing controls to test text input.
class TextInputTestWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
SampleTextField(),
SampleTextField(),
],
);
}
}
/// A text field with styling suitable for including in a TextInputTestWidget.
class SampleTextField extends StatelessWidget {
/// Creates a new sample text field.
const SampleTextField();
@override
Widget build(BuildContext context) {
return Container(
width: 200.0,
padding: const EdgeInsets.all(10.0),
child: TextField(
decoration: InputDecoration(border: OutlineInputBorder()),
),
);
}
}
После запуска примера приложения нажмите кнопку увеличения, которая затем выведет 0 1 2 3 4, а также адрес указателя на консоль.
Я приношу извинения, если это показалось ленивым, но я не разработчик flutter, на самом деле у меня нулевой опыт в этом, и сегодня был мой первый день, когда я прикоснулся к нему. Но разобраться в базовом коде и синтаксисе было не так уж сложно.
Комментарии:
1. Примечание: В моем коде на C я не использовал extern «C», потому что я скомпилировал его как C, а не C , поэтому искажение имен не было задействовано. В вашем случае, если вы компилируете C , сохраните внешнюю букву «C».
2. Здесь нет никаких причин использовать проект flutter-desktop-embedding. Если вы следовали инструкциям из какого-либо другого источника, который сказал вам это сделать, эти инструкции очень устарели. Текущие инструкции находятся в flutter.dev/desktop#create-a-new-project и просто предполагают использование
flutter create
, как и на любой другой платформе. (Кроме того, код, который вы называете «шаблонным», в значительной степени нет; это тестовый код для конкретных плагинов. Смотрите описание по адресу github.com/google/flutter-desktop-embedding/blob/master/testbed /… )3. @smorgan Спасибо за эту информацию. Я не смог найти никакой недавней документации по разработке настольных приложений, поэтому я выбрал проект flutter-desktop-embedding. Возможно, в конечном итоге я перепишу это руководство, используя более новые версии.
4. В моем случае он не видит сгенерированную dll — здесь DynamicLibrary.open(«mylib.dll «); Несмотря на то, что я вижу DLL с правильным именем, сгенерированным в каталоге сборки. Можете ли вы показать свой windows/CMakefile.txt также — чтобы посмотреть, как вы создаете свою библиотеку DLL?
5. @IllyaS Я не использовал CMake для создания своей библиотеки dll, а использовал Visual Studio. Но вы также можете использовать gcc. В Visual Studio вы должны перейти в настройки проекта, компоновщик-> система и установить subsystem в Windows, затем перейти к общим настройкам и вывести в dll вместо exe. Также в разделе C / C -> Генерация кода установите для библиотеки времени выполнения значение MT вместо MD. MT — это статическое связывание, которое может решить проблему, с которой вы столкнулись.