教程:Flutter 和 Rust混合编程,使用flutter_rust_bridge自动生成ffi代码

实践环境:Arch Linux

flutter_rust_bridge官方文档

Flutter环境配置教程 | Rust环境配置教程

记录使用flutter_rust_bridge遇到的一些坑。

假设我们已经配置了Fluuter与Rust环境

现在直接使用flutter_rust_bridge模板创建自己的项目

运行:

git clone https://github.com/Desdaemon/flutter_rust_bridge_template && cd flutter_rust_bridge_template

现在我们先让项目跑起来:

flutter run

添加新代码:

编辑 native/src/api.rs

安装代码生成器flutter_rust_bridge_codegen

cargo install flutter_rust_bridge_codegen

按照flutter_rust_bridge文档示例运行代码生成器:

flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart

发现报错了:

找不到stdbool.h文件

bridge_generated.dart报错信息:

void store_dart_post_cobject(int ptr)
package:flutter_rust_bridge_template/bridge_generated.dart

Not to be used by normal users, but has to be public for generated code

Copied from FlutterRustBridgeWireBase.

'NativeWire.store_dart_post_cobject' ('void Function(int)') isn't a valid override of 'FlutterRustBridgeWireBase.store_dart_post_cobject' ('void Function(Pointer<NativeFunction<Bool Function(Int64, Pointer<Void>)>>)').dartinvalid_override
stub.dart(21, 8): The member being overridden.

运行:

flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart \
                            --c-output ios/Runner/bridge_generated.h

发现还是老样子?怎么办?

解决方案:

添加CPATH环境变量,重新运行代码生成器:

export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include"
flutter_rust_bridge_codegen --rust-input native/src/api.rs \
                            --dart-output lib/bridge_generated.dart \
                            --c-output ios/Runner/bridge_generated.h

现在就正常了

打开lib/ffi.dart

看到有两处致命错误

报错信息:

package:flutter_rust_bridge_template/bridge_generated.dart

The name 'Native' is defined in the libraries 'package:flutter_rust_bridge_template/bridge_definitions.dart' and 'package:flutter_rust_bridge_template/bridge_generated.dart'.
Try removing the export of one of the libraries, or explicitly hiding the name in one of the export directives.
[abstract class Native, abstract class Native]
The name 'Native' is defined in the libraries 'package:flutter_rust_bridge_template/bridge_definitions.dart' and 'package:flutter_rust_bridge_template/bridge_generated.dart'.
Try using 'as prefix' for one of the import directives, or hiding the name from all but one of the imports.

解决方案:

去掉import 'bridge_definitions.dart'; 和 export 'bridge_definitions.dart';

lib/ffi.dart原代码:

// This file initializes the dynamic library and connects it with the stub
// generated by flutter_rust_bridge_codegen.

import 'dart:ffi';

import 'bridge_generated.dart';
import 'bridge_definitions.dart';
export 'bridge_definitions.dart';

// Re-export the bridge so it is only necessary to import this file.
export 'bridge_generated.dart';
import 'dart:io' as io;

const _base = 'native';

// On MacOS, the dynamic library is not bundled with the binary,
// but rather directly **linked** against the binary.
final _dylib = io.Platform.isWindows ? '$_base.dll' : 'lib$_base.so';

final Native api = NativeImpl(io.Platform.isIOS || io.Platform.isMacOS
    ? DynamicLibrary.executable()
    : DynamicLibrary.open(_dylib));

lib/ffi.dart修改后的代码:

// This file initializes the dynamic library and connects it with the stub
// generated by flutter_rust_bridge_codegen.

import 'dart:ffi';

import 'bridge_generated.dart';

// Re-export the bridge so it is only necessary to import this file.
export 'bridge_generated.dart';
import 'dart:io' as io;

const _base = 'native';

// On MacOS, the dynamic library is not bundled with the binary,
// but rather directly **linked** against the binary.
final _dylib = io.Platform.isWindows ? '$_base.dll' : 'lib$_base.so';

final Native api = NativeImpl(io.Platform.isIOS || io.Platform.isMacOS
    ? DynamicLibrary.executable()
    : DynamicLibrary.open(_dylib));

一切正常:

main.dart调用rust函数

main.dart完整代码:

import 'package:flutter/material.dart';
import 'ffi.dart' if (dart.library.html) 'ffi_web.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // These futures belong to the state and are only initialized once,
  // in the initState method.
  late Future<Platform> platform;
  late Future<bool> isRelease;

  late Future<String> test;

  @override
  void initState() {
    super.initState();
    platform = api.platform();
    isRelease = api.rustReleaseMode();

    test = api.test();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text("You're running on"),
            FutureBuilder<List<dynamic>>(
                future: Future.wait([test]),
                builder: (context, snap) {
                  final data = snap.data;
                  if (data == null) {
                    return const Text("Loading");
                  }

                  return Text('${data[0]}');
                }),

            // To render the results of a Future, a FutureBuilder is used which
            // turns a Future into an AsyncSnapshot, which can be used to
            // extract the error state, the loading state and the data if
            // available.
            //
            // Here, the generic type that the FutureBuilder manages is
            // explicitly named, because if omitted the snapshot will have the
            // type of AsyncSnapshot<Object?>.
            FutureBuilder<List<dynamic>>(
              // We await two unrelated futures here, so the type has to be
              // List<dynamic>.
              future: Future.wait([platform, isRelease]),
              builder: (context, snap) {
                final style = Theme.of(context).textTheme.headline4;
                if (snap.error != null) {
                  // An error has been encountered, so give an appropriate response and
                  // pass the error details to an unobstructive tooltip.
                  debugPrint(snap.error.toString());
                  return Tooltip(
                    message: snap.error.toString(),
                    child: Text('Unknown OS', style: style),
                  );
                }

                // Guard return here, the data is not ready yet.
                final data = snap.data;
                if (data == null) return const CircularProgressIndicator();

                // Finally, retrieve the data expected in the same order provided
                // to the FutureBuilder.future.
                final Platform platform = data[0];
                final release = data[1] ? 'Release' : 'Debug';
                final text = const {
                      Platform.Android: 'Android',
                      Platform.Ios: 'iOS',
                      Platform.MacApple: 'MacOS with Apple Silicon',
                      Platform.MacIntel: 'MacOS',
                      Platform.Windows: 'Windows',
                      Platform.Unix: 'Unix',
                      Platform.Wasm: 'the Web',
                    }[platform] ??
                    'Unknown OS';
                return Text('$text ($release)', style: style);
              },
            )
          ],
        ),
      ),
    );
  }
}

运行项目:

flutter run

猜你喜欢

转载自blog.csdn.net/love906897406/article/details/128702597