当前位置: 首页 > news >正文

Flutter 直接调用so动态库,或调用C/C++源文件内函数

开发环境

MacBook Pro Apple M2 Pro | macOS Sonoma 14.0
Android Studio Giraffe | 2022.3.1 Patch 1
XCode Version 15.0
Flutter 3.13.2 • channel stable
Tools • Dart 3.1.0 • DevTools 2.25.0


先说下历程,因为我已经使用了Flutter3+的版本,起初了解到Flutter调用C/C++可以使用dart原生的 ‘dart:ffi’ 库,于是按照找到的一些文档使用,结果无论如何都会报错:“Failed to load dynamic library”,也就是在 DynamicLibrary.open('libxxx.so') 的阶段就失败了,我把 .so 文件尝试放过好几个目录 /assets/libs//lib//android/app/src/main/jniLibs/,最终都会报这个错误,现在才知道库文件需要放在你根本想象不到的地方!!!

尝试了一些方法还是不行后我放弃了 ffi,想着用 Flutter 的 MethodChannel 桥接 android/ios 原生,再让原生去调 native 层。一顿操作把Android端的搞定了,当然中间涉及到 Kotlin和C++ 层的数据类型映射的痛苦,而且业务函数也不像一个 helloworld 那么简单,别提有多痛苦了。当我再转身去搞iOS端的时候看到了iOS应该使用静态库 .a/.framework 格式的消息,如果使用动态库上架App Store是会被驳回的!native端不是我写的,我只有动态库,虽然之前写过双端的密钥插件有点经验,但是iOS毕竟咱过于陌生需要遍地找文档,过程实在过于痛苦,于是非常不甘心地又尝试起了 ffi 的方案。

在反复尝试了各种文档和博客后,我新建了一个Flutter C++的插件项目终于跑通了,并且找到了生成的库文件存放的位置。其实用法很简单,只是走错了路让我痛苦了一遍又一遍!!!
Flutter 和 Dart 的 官方文档 都只讲了如何调用 C/C++函数,没有提到如何直接调用 动态库或静态库。

中间我尝试了用Android原生 jni 调so库、用xcode写C++代码生成静态库给iOS调用、cmake交叉编译各平台库…因为 jni 的方式需要 包名、类名、入参类型、返回值类型 完全对应才能正确映射,jni 几百年不用一次又去捡了一遍这些细节知识,我真的椒麻了!!!

下面分别示例了 Flutter直接调用so动态库Flutter调用C/C++源文件内函数,第一种方式更为简单,因为省去了编译相关的配置


Flutter直接调用so动态库

先讲一下流程:

  1. 在正确的地方放置 .so 文件
  2. 编写 dart 代码调用 native 函数
  3. 在 dart 代码中调用 dart 映射的函数

案例:

我创建了一个 C++ 项目,使用一个 .cpp 文件写了一个 add 函数,add 函数有 两个 int 类型的入参,和一个 int 类型的出参,最终打包了 libnative_add.so 文件给 Flutter项目的 Android和iOS两端使用。

cpp 代码如下:

#include <stdio.h>// extern "C" 是移动端调用 C++ 代码需要的,调用 C 代码则不需要
extern "C" {int32_t add(int32_t a, int32_t b) {return a + b;}
}

映射成 dart 代码也就是:


int add(int a, int b) {return a + b;
}

一、.so文件放在哪个目录

这可以说是坑了我的最关键的一步,动态库应该放的目录是:

项目名/build/app/intermediates/merged_native_libs/debug/out/lib/,因为我是新建的插件项目,所以我的目录层级会多一级。

.so文件目录

对应的架构和原生一样分别放在自己的目录,比如 armeabi-v7a、arm64-v8a、x86_64,这里面也涉及真机和模拟器的一些问题

build 目录可能在 Android Studio 内看不到,到文件夹下去操作就行,release 阶段也需要在对应目录放置库文件。是不是非常意想不到,毕竟这个目录一旦 clean 就没了,不过可以放在其他目录写个 gradle 脚本拷贝过去

顺便提一下 Flutter 很多文件的操作都需要在“非正常目录”去操作,比如说用xcode编写iOS端插件需要在 /ios/.symlinks/... 目录去找项目,有时候 Android Studio 还会抽疯,无法直接在 Flutter 项目里 “从 Android Studio 打开 android 目录” 或者 “从 XCode 打开 iOS 目录”

二、加载.so文件并创建映射函数

这一步是在写dart代码的 项目名/lib/ 目录,最好额外创建一个文件去处理这块儿内容,比如文件:native.dart

点解释了: 关于这两行代码的含义

点详细讲解了: ffi 调用native函数的两种方式和泛型参数的两种写法

final addLib = Platform.isAndroid ? DynamicLibrary.open('libnative_add.so') : DynamicLibrary.process();final addFunc = addLib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>('add');

三、调用函数

导入文件,直接调用就行

import '../native.dart';int result = addFunc(1, 2);

到这里代码就结束了,很简单对吧,我搞了一天!!!


四、解释一下这两行代码的含义

第一行是加载动态库文件。Android和iOS的方式不一样,包括Mac或者Windows上也需要不同的代码兼容(这里只包含Android iOS的),动态库的原文件名就是 “libnative_add.so”,我没尝试过类似Java加载动态库的这种写法能不能行 “native_add”

第二行是调用函数 add 。泛型部分后面有详细讲解。


五、ffi 调用native函数的两种方式和泛型参数的两种写法

ffi 调用native函数的两种方式

  1. ffi 调用native函数方式一
final int Function(int x, int y) addFunc = addLib.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('add').asFunction();
  1. ffi 调用native函数方式二
final addFunc = addLib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int>('add');

其中关键函数有两个,方式一是 lookup().asFunction(),方式二是 lookupFunction()。函数的出参和入参都是 Function 的形式。

官方使用的方式一,这个应该是版本原因,方式二的函数估计是后面版本新增的扩展方法,反正我是用的这个函数。这几个函数的原定义如下:

// 方式一的函数,需要配合 asFunction 函数使用
external Pointer<T> lookup<T extends NativeType>(String symbolName);extension NativeFunctionPointer<NF extends Function> on Pointer<NativeFunction<NF>> {external DF asFunction<('NF') DF extends Function>({bool isLeaf = false});
}// 方式二的函数
external F lookupFunction<T extends Function, F extends Function>(String symbolName, {bool isLeaf = false});

再解释一下这两个函数的泛型部分

方式一的函数,泛型只有一个参数,传入的是 native层函数写法的出参和入参类型,且需要用 NativeFunction 类包裹,变量接收的是 dart层函数写法的出入参类型。涉及到 dart 和 C/C++ 的数据类型对照看这里。

方式二的函数,泛型有两个参数,泛型参数一是 native层函数写法的出入参类型,泛型参数二是 dart层函数写法的出入参类型

是不是感觉有点绕,多看几眼就适应了。

至于泛型参数部分还有其他写法,就是将泛型参数用 typedef 关键字定义出去。举个完整的栗子来清晰一下两种写法:

  1. 最原始的写法
final lib = Platform.isAndroid ? DynamicLibrary.open('libnative_add.so') : DynamicLibrary.process();// 方式一调用
final int Function(int x, int y) addFunc = addLib.lookup<NativeFunction<Int32 Function(Int32, Int32)>>('add').asFunction();// 方式二调用
final addFunc = addLib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>('add');
  1. 视觉优化后的写法
typedef AddNative = Int32 Function(Int32, Int32); //定义native层函数写法的出入参类型
typedef AddDart = int Function(int, int); //dart层函数写法的出入参类型final lib = Platform.isAndroid ? DynamicLibrary.open('libnative_add.so') : DynamicLibrary.process();// 方式一调用
final addDart addFunc = addLib.lookup<NativeFunction<AddNative>>('add').asFunction();// 方式二调用
final addFunc = addLib.lookupFunction<AddNative, AddDart>('add');

现在是不是理解我为什么会选用方式二了,因为语法层面会更清晰明了一些!




Flutter调用C/C++源文件内函数

依旧是使用官方的 ‘dart:ffi’ 调用。

很简单,全程在 android 目录下操作,首先讲一下流程:

  1. 创建 ‘xx.cpp’ 文件,编写函数代码
  2. 创建 ‘CMakeLists.txt’ 文件,编写打包 动态库或静态库 代码
  3. 配置 cmake 编译环境和平台
  4. 使用 ffi 调用 native 函数

一、编写 C/C++ 代码

这里只演示了 C++,仅仅是 C 的话自行修改,注:C++ 可以不需要头文件

#include <stdio.h>extern "C" {int32_t add(int32_t a, int32_t b) {return a + b;}
}

二、编写 CMakeLists.txt 代码

项目名/android/ 创建文件 CMakeLists.txt
以下文件打出来的包在如下位置,如有插件会多一层目录:
项目名/build/app/intermediates/merged_native_libs/debug/out/lib/

cmake_minimum_required(VERSION 3.4.1)  # 版本根据自己的需要进行修改add_library(# 编译打包出来的lib文件名称,以下打包出来为:libnative_add.sonative_add# 动态库使用:SHARED、静态库使用:STATICSHARED# 源文件可以放在别的地方native_add.cpp 
)

三、配置 cmake 编译环境和平台

打开 build.gradle,在 android 节点下配置:

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

四、使用 ffi 调用 native 函数

参照上面调用 .so 的代码

// ffi 加载动态库,并映射 native 函数
final addLib = Platform.isAndroid ? DynamicLibrary.open('libnative_add.so') : DynamicLibrary.process();final addFunc = addLib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>('add');
// 导入文件,调用函数
import '../native.dart';int result = addFunc(1, 2);

文末 !!!!!!!!!!加粗吐槽跨平台针对原生兼容的坑,官方文档有重要遗漏且不清晰、官方没有足够完整的案例、对于平常只接触单一平台的人很不友好!!!!!!!!!

相关文章:

Flutter 直接调用so动态库,或调用C/C++源文件内函数

开发环境 MacBook Pro Apple M2 Pro | macOS Sonoma 14.0 Android Studio Giraffe | 2022.3.1 Patch 1 XCode Version 15.0 Flutter 3.13.2 • channel stable Tools • Dart 3.1.0 • DevTools 2.25.0 先说下历程&#xff0c;因为我已经使用了Flutter3的版本&#xff0c;起初…...

elasticsearch(ES)分布式搜索引擎03——(RestClient查询文档,ES旅游案例实战)

目录 3.RestClient查询文档3.1.快速入门3.1.1.发起查询请求3.1.2.解析响应3.1.3.完整代码3.1.4.小结 3.2.match查询3.3.精确查询3.4.布尔查询3.5.排序、分页3.6.高亮3.6.1.高亮请求构建3.6.2.高亮结果解析 4.旅游案例4.1.酒店搜索和分页4.1.1.需求分析4.1.2.定义实体类4.1.3.定…...

198、RabbitMQ 的核心概念 及 工作机制概述; Exchange 类型 及 该类型对应的路由规则

JMS 也是一种消息机制 AMQP ( Advanced Message Queuing Protocol ) 高级消息队列协议 ★ RabbitMQ的核心概念 Connection&#xff1a; 代表客户端&#xff08;包括消息生产者和消费者&#xff09;与RabbitMQ之间的连接。 Channel&#xff1a; 连接内部的Channel。 Exch…...

系统架构设计:18 论基于DSSA的软件架构设计与应用

目录 一 特定领域软件架构DSSA 1 DSSA 2 DSSA的基本活动和产物 (1)DSSA的基本活动和产物...

Android原生实现控件outline方案(API28及以上)

Android控件的Outline效果的实现方式有很多种&#xff0c;这里介绍一下另一种使用Canvas.drawPath()方法来绘制控件轮廓Path路径的实现方案&#xff08;API28及以上&#xff09;。 实现效果&#xff1a; 属性 添加Outline相关属性&#xff0c;主要包括颜色和Stroke宽度&…...

ROS学习笔记(六)---服务通信机制

1. 服务通信是什么 在ROS中&#xff0c;服务通信机制是一种点对点的通信方式&#xff0c;用于节点之间的请求和响应。它允许一个节点&#xff08;服务请求方&#xff09;向另一个节点&#xff08;服务提供方&#xff09;发送请求&#xff0c;并等待响应。 服务通信机制在ROS中…...

常见的C/C++开源QP问题求解器

1. qpSWIFT qpSWIFT 是面向嵌入式和机器人应用的轻量级稀疏二次规划求解器。它采用带有 Mehrotra Predictor 校正步骤和 Nesterov Todd 缩放的 Primal-Dual Interioir Point 方法。 开发语言&#xff1a;C文档&#xff1a;传送门项目&#xff1a;传送门 2. OSQP OSQP&#…...

前端axios发送请求,在请求头添加参数

1.在封装接口传参时&#xff0c;定义形参&#xff0c;params是正常传参&#xff0c;name则是我想要在请求头传参 export function getCurlList (params, name) {return request({url: ********,method: get,params,name}) } 2.接口调用 const res await getCurlList(params,…...

CTF Misc(3)流量分析基础以及原理

前言 流量分析在ctf比赛中也是常见的题目&#xff0c;参赛者通常会收到一个网络数据包的数据集&#xff0c;这些数据包记录了网络通信的内容和细节。参赛者的任务是通过分析这些数据包&#xff0c;识别出有用的信息&#xff0c;例如登录凭据、加密算法、漏洞利用等等 工具安装…...

Telink泰凌微TLSR8258蓝牙开发笔记(二)

在开发过程中遇到了以下问题&#xff0c;记录一下 1.在与ios手机连接后&#xff0c;手机app使能notify&#xff0c;设备与手机通过write和notify进行数据交换&#xff0c;但是在连接传输数据一端时间后&#xff0c;设备收到write命令后不能发出notify命令&#xff0c;打印错误…...

vue3+elementPlus:el-tree复制粘贴数据功能,并且有弹窗组件

在tree控件里添加contextmenu属性表示右键点击事件。 因右键自定义菜单事件需要获取当前点击的位置&#xff0c;所以此处绑定动态样式来控制菜单实时跟踪鼠标右键点击位置。 //html <div class"box-list"><el-tree ref"treeRef" node-key"id…...

JTS:10 Crosses

这里写目录标题 版本点与线点与面线与面线与线 版本 org.locationtech.jts:jts-core:1.19.0 链接: github public class GeometryCrosses {private final GeometryFactory geometryFactory new GeometryFactory();private static final Logger LOGGER LoggerFactory.getLog…...

MySQL中的SHOW FULL PROCESSLIST命令

在MySQL数据库管理中&#xff0c;理解和监控当前正在执行的进程是至关重要的一环。MySQL提供了一系列强大的工具和命令&#xff0c;使得这项任务变得相对容易。其中&#xff0c;SHOW FULL PROCESSLIST命令就是一个非常有用的工具&#xff0c;它可以帮助我们查看MySQL服务器中的…...

VsCode 常见的配置、常用好用插件

1、自动保存&#xff1a;不用装插件&#xff0c;在VsCode中设置一下就行 2、设置ctr滚轮改变字体大小 3、设置选项卡多行展示 这样打开了很多个文件&#xff0c;就不会导致有的打开的文件被隐藏 4、实时刷新网页的插件&#xff1a;LiveServer 5、open in browser 支持快捷键…...

深度学习问答题(更新中)

1. 各个激活函数的优缺点&#xff1f; 2. 为什么ReLU常用于神经网络的激活函数&#xff1f; 在前向传播和反向传播过程中&#xff0c;ReLU相比于Sigmoid等激活函数计算量小&#xff1b;避免梯度消失问题。对于深层网络&#xff0c;Sigmoid函数反向传播时&#xff0c;很容易就…...

JavaScript 笔记: 函数

1 函数声明 2 函数表达式 2.1 函数表达式作为property的value 3 箭头函数 4 构造函数创建函数&#xff08;不推荐&#xff09; 5 function 与object 5.1 typeof 5.2 object的操作也适用于function 5.3 区别于⼀般object的⼀个核⼼特征 6 回调函数 callback 7 利用function的pr…...

2023NOIP A层联测9-天竺葵

天竺葵/无法阻挡的子序列/很有味道的题目 我们称一个长度为 k k k 的序列 c c c 是好的&#xff0c;当且仅当对任意正整数 i i i 在 [ 1 , k − 1 ] [1,k-1] [1,k−1] 中&#xff0c;满足 c i 1 > b i c i c_{i1}>b_i \times c_i ci1​>bi​ci​&#xff0c; …...

react antd table表格点击一行选中数据的方法

一、前言 antd的table&#xff0c;默认是点击左边的单选/复选按钮&#xff0c;才能选中一行数据&#xff1b; 现在想实现点击右边的部分&#xff0c;也可以触发操作选中这行数据。 可以使用onRow实现&#xff0c;样例如下。 二、代码 1.表格样式部分 //表格table样式部分{…...

【VUEX】最好用的传参方式--Vuex的详解

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于VuexElementUI的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Vuex是什么 1.定义 2…...

【.net core】yisha框架 SQL SERVER数据库 反向递归查询部门(子查父)

业务service.cs中ListFilter方法中内容 //反向递归查询部门列表List<DepartmentEntity> departmentList await departmentService.GetReverseRecurrenceList(new DepartmentListParam() { Ids operatorInfo.DepartmentId.ToString() });if (departmentList ! null &am…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...