在 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 )
- 指定源码对应的库是哪个库
- 指定库的类型, 这里是动态库, 所以用 SHARED
- 指定源码目录
然后因为我们使用了 cmake, 为了让安卓项目知道, 我们需要修改 gradle 文件
android{
// ...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
这里在 android 节点下, 添加属性即可, 这里是指定 Cmake 使用的文件
接着就可以运行项目了, 和 android 中一样

简单总结
现在 ffi 处于初始阶段, 还有诸多不足.
比如, 文档的缺失, 现在如何传递字符串,数组都是问题, 虽然有结构体的定义, 也能看到部分说明, 但没有简单的示例帮助开发者快速使用.
只有基本数据类型, 目前可能还不需要借用 c 来解决, 未来则要看 ffi 会开放到什么程度.
后记
项目地址: https://github.com/caijinglong/example_for_flutter_ffi
在 flutter 上使用 c 代码 - (一) 有源码的项目的更多相关文章
- web service上传参数代码实例
web service上传参数代码实例 这次做的项目用到webservice比较多,最开始在网上看的参考dome,发现都不行,后来发现安卓4.0以后有很大的不同,在做传参时,有些东西需要注意: 第一, ...
- [实战] Flutter 上的内存泄漏监控
一.前言 Flutter 所使用的 Dart 语言具有垃圾回收机制,有垃圾回收就避免不了会内存泄漏. 在 Android 平台上有个内存泄漏检测工具 LeakCanary, 它可以方便地在 debug ...
- salesforce 零基础学习(五十三)多个文件生成一个zip文件(使用git上封装的代码)
此篇参考git代码:https://github.com/pdalcol/Zippex 学习salesforce可以访问一个朋友的网站:https://www.xgeek.net 首先感谢git上提供 ...
- JavaScript返回上一页代码区别
JavaScript返回上一页代码区别: window.history.go(-1); //返回上一页 window.history.back(); //返回上一页 //如果要强行刷新的话就是:win ...
- [html5+java]文件异步读取及上传核心代码
html5+java 文件异步读取及上传关键代码段 功能: 1.多文件文件拖拽上传,file input 多文件选择 2.html5 File Api 异步FormData,blob上传,图片显示 3 ...
- ExtJS + fileuploadfield上传文件代码
后台服务端接收文件的代码: /** * 后台上传文件处理Action */ @RequestMapping(value = "/uploadFile", method=Reques ...
- 解放双手:如何在本地调试远程服务器上的Node代码
写在前面 谈到node断点调试,目前主要有三种方式,通过node内置调试工具.通过IDE(如vscode).通过node-inspector,三者本质上差不多.本文着重点在于介绍 如何在本地通过nod ...
- 在Windows Server 2008上部署SVN代码管理总结
这段时间在公司开发Flex程序,所以使用TortoiseSVN作为团队代码管理器,今天在公司服务器上部署SVN服务器,并实验成功,总结如下: 服务器环境: 操作系统:Windows Server 20 ...
- JAE京东云引擎Git上传管理代码教程和京东云数据库导入导出管理
文章目录 Git管理准备工作 Git工具上传代码 发布代码装程序 mywebsql管理 京东云引擎小结 JAE京东云引擎是京东推出的支持Java.Ruby.Python.PHP.Node.js多语 ...
随机推荐
- 英语dialogite红纹石dialogite菱锰矿
红纹石又称菱锰矿(dialogite)属于三方晶系.晶体呈菱面体,通常呈粒状.块状或结核状.玫瑰色,容易氧化而转变成褐黑色.玻璃光泽.解理平行菱面体{1011}完全.硬度3.5-4.5.比重3.6-3 ...
- JAVA I/O系统 Thinking in Java 之 File类
File类的文件具有一定的误导性,我们可能会认为它指代的是文件,实际上并非如此.它技能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称.如果它指的是一个文件集,我们就可以对此集合调用list ...
- 建议各位亲使用LocalDateTime而不使用Date哦
在项目开发过程中经常遇到时间处理,但是你真的用对了吗,理解阿里巴巴开发手册中禁用static修饰SimpleDateFormat吗 通过阅读本篇文章你将了解到: 为什么需要LocalDate.Loca ...
- 判断cms-暴库-后台查找-密码破解
1.判断网站CMS类型 判断目标 使用工具: 脚本语言 wvs 御剑 domain明小子 操作系统 wwwscan 啊d pangolin(穿山甲) 搭建平台 站长工具 netpark ...
- [转]【response】HttpServletResponse接口
创建时间:6.19 & 6.24 1.HttpServletResponse概述 我们在创建Servlet时会覆盖service()方法,或doGet()/doPost(),这些方法都有两个参 ...
- Java八大排序之基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分 ...
- vs2017 curl7.6编译
nmake /f Makefile.vc mode=static VC=15 MACHINE=x86 nmake /f Makefile.vc mode=dll VC=15 MACHINE=x86 c ...
- PHP将图片转base64格式函数
base64的好处是什么?今天在跟小伙伴讨论这个问题,要是全站用Php把图片转为base64行不行? 1. 提升性能: 网页上的每一个图片,都是需要消耗一个 http 请求下载而来的, 图片的下载始终 ...
- nginx docker 命令: command not found
1. ps: command not found 使用如下命令安装 apt-get update && apt-get -y install procps 2. vim: comman ...
- [KCOJ3393]上马
题目描述 Description Chicken在IEC(International Equestrianism Competition(国际马术表演赛))惨跪,没有成功的上到马,他深刻的记得他的选手 ...