在flutter上使用c代码(带项目源码)

在 flutter 的 1.10.x 后的分支, dart:ffi 被并入 flutter, 现在 flutter 中也可以使用 ffi 了。


这东西是啥玩意呢, 就是让 dart 可以直接调用 c/c++ 代码等东西的库, FFI(foreign function interface), 官方文档在这里。

但是在当前版本中, 这东西在官方说明中依然处于技术预览版, 就是可用, 但后续不保证 api 不变更。

开发环境

首先我是 mac 系统, windows 系统不保证脚本的可用和工具的可用, linux 的话可能一些必要工具需要使用自己平台的包管理工具, 并且涉及到 ios 部分, 必须使用 mac。

所有需要的工具包

Xcode(或 XcodeBuild 命令行工具)
brew
clang
cmake
Android 工具链
Android SDK
NDK
Android Studio(可选)
Gradle
Flutter 工具链
SDK 1.10.x+
vscode(可选, 这东西看你的情况,作为示例的话只要是文本编辑器即可, 我本人使用这个作为主要的文本编辑器)

这里说的是包含后续所有用到的东西, 并不仅仅是本文。 其中对于 flutter 开发者可能需要单独安装的应该只有 NDK 和 Cmake, 这两个东西是包含在 android sdk 下的, 可以使用 android studio 下载, 也可以单独下载

ffi 的简单介绍

根据官方文档说明

可以理解为, 将 c 的类型和 dart 的类型关联起来, 然后 ffi 会在内部将两端关联起来, 完成调用

基本就是对应 c 中的类型, 对应 Void 各种长度的 有无符号的整型, 单双精度浮点, 指针, 方法

转化的过程

c 源码核心就这点, 其他的都做不知即可

void hello_world()
{
    printf("Hello World\n");
}
导包, 这个是第一步要做的
import 'dart:ffi' as ffi;
// 定义一个ffi类型
typedef hello_world_func = ffi.Void Function();

// 将ffi类型定义为dart类型
typedef HelloWorld = void Function();

// 打开动态库, dylib是mac上的动态库的后缀
final dylib = ffi.DynamicLibrary.open('hello_world.dylib');

// 这里是最难理解的一步, 后面会详细解说
final HelloWorld hello = dylib
    .lookup<ffi.NativeFunction<hello_world_func>>('hello_world')
    .asFunction();

// 调用
hello();

详细理解转化过程

这里以 lookup 方法为切入点,详细理解下这里做了什么, 以便于后面我们可以自行完成这个过程

lookup 方法签名如下:

external Pointer<T> lookup<T extends NativeType>(String symbolName);

参数

很好理解, 传入一个方法名, 让我们能找到 c 方法

泛型

这个是方法的类型签名的 dart:ffi 表现形式.

c 方法的签名是这样的: void hello_world(), 所以我们就需要一个对应的类型, 也就是上面定义的 ffi 类型

ffi.Void Function()

返回类型

这里的返回值是用于在实际调用时,转化 c 方法的返回值为 dart 的类型来使用的, 所以就是对应的 dart 类型

/// 定义是这样的
void Function()

/// 接收的asFunction方法
final void Function() hello = XXXX;

写起来的时候可能是这样的,

实例

extern "C" {
// __attribute__((visibility("default"))) __attribute__((used)) // 虽然说需要这行, 但是没这行也没报错
int32_t native_add(int32_t x, int32_t y) { return x + y; }

double double_add(double x, double y) { return x + y; }
}
import 'dart:ffi';

final DynamicLibrary dylib = Platform.isAndroid
    ? DynamicLibrary.open("libnative_add.so")
    : DynamicLibrary.open("native_add.framework/native_add");

final int Function(int x, int y) nativeAdd = dylib
    .lookup<NativeFunction<Int32 Function(Int32, Int32)>>("native_add")
    .asFunction();

final double Function(double, double) doubleAdd = dylib
    .lookup<NativeFunction<Double Function(Double, Double)>>("double_add")
    .asFunction();

打包和运行

在 dart vm 中,可以有多种方案, 只要能编译出 dylib 即可

官方的hello world 示例中是直接使用 make, 内部使用 gcc 打包编译

这里有一个脚本,是设置 dylib 的目录到环境变量中, 以便于运行时可以找到动态库

在 flutter 中使用

接着就要开始在 flutter 中使用了, 和在 dart vm 中使用不一样, 不能使用环境变量, 而是需要将库置入到项目中

创建仓库

直接使用 $ flutter create -t plugin native_add 的方式即可

cpp 文件

native_add.cpp

#include <stdint.h>

extern "C" {
// __attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t x, int32_t y) { return x + y; }

double double_add(double x, double y) { return x + y; }
}

dart 文件

final DynamicLibrary dylib = Platform.isAndroid
    ? DynamicLibrary.open("libnative_add.so")
    : DynamicLibrary.open("native_add.framework/native_add");

final int Function(int x, int y) nativeAdd = dylib
    .lookup<NativeFunction<Int32 Function(Int32, Int32)>>("native_add")
    .asFunction();

final double Function(double, double) doubleAdd = dylib
    .lookup<NativeFunction<Double Function(Double, Double)>>("double_add")
    .asFunction();
界面:
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter = nativeAdd(_counter, 1);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            Text(
                "native double value = ${doubleAdd(_counter.toDouble(), _counter.toDouble())}"),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

ios

ios 中, 直接将 cpp 文件置入 ios/classes 文件夹内即可, 然后因为 podspec 中包含默认配置的原因, 这个文件会被自动引入项目

s.source_files = 'Classes/**/*'

Android

android 中其实有两种方法, 一是用传统的 ndk 方式, 就是 Android.mk 那种方案, 我们略过这种方案, 因为配置比较复杂, 我们使用第二种方案, 官方推荐的 cmake 方案

因为 ios 中, 文件被置入源码中, 我这里直接使用相对路径去引入这个文件

CMakeLists.txt:

cmake_minimum_required(VERSION 3.4.1)  # for example

add_library( native_add

    # Sets the library as a shared library.
    SHARED

    # Provides a relative path to your source file(s).
    ../ios/Classes/native_add.cpp )
  1. 指定源码对应的库是哪个库
  2. 指定库的类型, 这里是动态库, 所以用 SHARED
  3. 指定源码目录

然后因为我们使用了 cmake, 为了让安卓项目知道, 我们需要修改 gradle 文件

android{
    // ...
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

这里在 android 节点下, 添加属性即可, 这里是指定 Cmake 使用的文件

接着就可以运行项目了, 和 android 中一样

简单总结

现在 ffi 处于初始阶段, 还有诸多不足.

比如, 文档的缺失, 现在如何传递字符串,数组都是问题, 虽然有结构体的定义, 也能看到部分说明, 但没有简单的示例帮助开发者快速使用.

只有基本数据类型, 目前可能还不需要借用 c 来解决, 未来则要看 ffi 会开放到什么程度.

后记

项目地址: https://github.com/caijinglong/example_for_flutter_ffi

原文地址:https://www.kikt.top/posts/flutter/ffi/1-first-party/


本博客所有文章如无特别注明均为原创。作者:flutter教程网复制或转载请以超链接形式注明转自 Flutter教程网
原文地址《在flutter上使用c代码(带项目源码)
分享到:更多

相关推荐


Flutter教程网 官方QQ群:874592746

扫描下面二维码 加入Flutter教程网微信群:


发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(0)