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);

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/155293.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

servlet基础知识

目录 什么是servlet概念/定义作用 servlet容器概念/是什么作用如何配置和管理 servlet生命周期有哪些生命周期每个周期中可以执行哪些操作 创建和编写servlet如何创建一个简单的servletservlet类的结构是什么样的如何处理HTTP请求和响应 servlet映射和URL模式什么是servlet映射…

Cookie和Session

目录 一、Cookie是什么&#xff1f; 二、Session是什么&#xff1f; 2.1 Session使用流程 三、Cookie 与 Session 的区别 四、核心方法 4.1 HttpServlet中关于Session的方法 4.2 HttpSession类中的方法 4.3 Cookie类中的方法 一、Cookie是什么&#xff1f; Cookie是浏览器在本…

微宏科技基于 KubeSphere 的微服务架构实践

作者&#xff1a;尹珉&#xff0c;KubeSphere Ambassador、contributor&#xff0c;KubeSphere 社区用户委员会杭州站站长。 公司简介 杭州微宏科技有限公司于 2012 年成立&#xff0c;专注于业务流程管理和自动化(BPM&BPA)软件研发和解决方案供应商。创始团队毕业于浙江大…

NodeJs中使用JSONP和Cors实现跨域

跨域是为了解决浏览器请求域名&#xff0c;协议&#xff0c;端口不同的接口&#xff0c;相同的接口是不需要实现跨域的。 1.使用JSONP格式实现跨域 实现步骤 动态创建一个script标签 src指向接口的地址 定义一个函数和后端调用的函数名一样 实现代码 -- 在nodejs中使用http内…

刷题用到的非常有用的函数c++(持续更新)

阅读导航 字符串处理类一、stoi()&#xff08;将字符串转换为整数类型&#xff09;二、to_string()&#xff08;将整数类型转换为字符串类型&#xff09;三、stringstream函数&#xff08;将一个字符串按照指定的分隔符进行分词&#xff09; 字符串处理类 一、stoi()&#xff…

nodejs+vue家教管理系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1nodejs简介 4 2.2 express框架介绍 6 2.3 B/S结构 4 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性…

BGP服务器租用腾讯云和阿里云价格对比

BGP云服务器像阿里云和腾讯云均是BGP多线网络&#xff0c;速度更快延迟更低&#xff0c;阿里云BGP服务器2核2G3M带宽优惠价格108元一年起&#xff0c;腾讯云BGP服务器2核2G3M带宽95元一年起&#xff0c;阿腾云atengyun.com分享更多云服务器配置如2核4G、4核8G、8核16G等配置价格…

探索 Redis 与 MySQL 的双写问题

在日常的应用开发中&#xff0c;我们经常会遇到需要使用多种不同类型的数据库管理系统来满足各种业务需求。其中最典型的就是Redis和MySQL的组合使用。 这两者拥有各自的优点&#xff0c;例如Redis为高性能的内存数据库提供了极快的读写速度&#xff0c;而MySQL则是非常强大的…

Soul CEO张璐团队以用户安全为核心,探索社交平台安全治理新路径

“认同感”,是现代年轻人当下的核心社交需求之一,作为年轻人喜爱的新型开放式社交平台,Soul APP为年轻人们提供了一个自在表达、轻松互动的平台,为用户带来了志趣相投、精神共鸣的高质量网络连接。在Soul日活近千万的用户中,超过七成为Z世代年轻群体,如何能够为Z世代提供更安全…

lv8 嵌入式开发-网络编程开发 16 多路复用poll函数

目录 1 多路复用的多种实现方式 2 poll 2.1 poll 函数应用 3 epoll 函数族&#xff08;效率最高&#xff09; 3.1 epoll_create 创建epoll句柄 3.2 epoll_ctl epoll句柄控制接口 3.3 epoll_wait 等待 epoll 文件描述符上的 I/O 事件 3.4 epoll 函数应用 1 多路复用的多…

智能优化算法常用指标一键导出为EXCEL,CEC2017函数集最优值,平均值,标准差,最差值,中位数,秩和检验,箱线图...

声明&#xff1a;对于作者的原创代码&#xff0c;禁止转售倒卖&#xff0c;违者必究&#xff01; 之前出了一篇关于CEC2005函数集的智能算法指标一键统计&#xff0c;然而后台有很多小伙伴在询问其他函数集该怎么调用。今天采用CEC2017函数集为例&#xff0c;进行展示。 为了突…

高效节能双冷源空调架构在某新建数据中心项目中的应用

随着互联网、通信、金融等行业的发展&#xff0c;数据中心产业迈入高质量发展新阶段&#xff0c;在国家“双碳”战略目标和“东数西算”工程的有力指引下&#xff0c;数据中心加快向创新技术、强大算力、超高能效为特征的方向演进。数据中心已经成为支撑经济社会数字化转型必不…

导引服务机器人 通用技术条件

声明 本文是学习GB-T 42831-2023 导引服务机器人 通用技术条件. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 6 检验规则 6.1 检验项目 检验分为型式检验和出厂检验。检验项目见表2。 表 2 检验项目 序号 检验项目 技术要求 检验方法 出厂检验 型…

SpringBoot 如何使用 Ehcache 作为缓存

使用Spring Boot Sleuth进行分布式跟踪 在现代分布式应用程序中&#xff0c;跟踪请求和了解应用程序的性能是至关重要的。Spring Boot Sleuth是一个分布式跟踪解决方案&#xff0c;它可以帮助您在分布式系统中跟踪请求并分析性能问题。本文将介绍如何在Spring Boot应用程序中使…

RS485通讯方式-详解

RS485是美国电子工业协会&#xff08;EIA&#xff09;在1983年批准的一个新的平衡传输标准&#xff0c;也称作差分。 RS485总线通常采用两线间的电压差为2V到6V表示逻辑1&#xff0c;以两线间的电压差为-2V到-6V表示逻辑0。 这种总线以其差分传输方式而闻名&#xff0c;发送端在…

Redis之缓存一致性

Redis之缓存一致性 1 缓存更新策略1.1 内存淘汰1.2 过期删除1.3 主动更新1.4 三种缓存更新策略的对比 2 更新缓存的两种方式3 缓存更新策略的实现方式3.1 先更新DB&#xff0c;后更新缓存3.2 先更新DB&#xff0c;后删除缓存3.3 先更新缓存&#xff0c;后更新DB3.4 先删除缓存&…

【数据结构C/C++】稀疏矩阵的压缩

文章目录 什么是稀疏矩阵&#xff1f;使用C语实现对稀疏矩阵的压缩408考研各数据结构C/C代码&#xff08;Continually updating&#xff09; 什么是稀疏矩阵&#xff1f; 稀疏矩阵&#xff08;Sparse Matrix&#xff09;是一种矩阵&#xff0c;其中大多数元素都是零。与稠密矩…

蓝桥杯 使用sort排序(c++)

sort是一个C已经为我们实现好的工具&#xff0c;当我们要用它时&#xff0c;需要先引入一个算法的库—— < algorithm >。需要说明的是&#xff0c;sort可以排序任何类型的元素&#xff0c;包括我们自己定义的结构体。 我们将需要在C文件的开始位置加上&#xff1a; #in…

C++: 继承

学习目标 1.继承的概念及定义 2.基类和派生类对象赋值转换(切片) 3.继承中的作用域(隐藏/重定义) 4.派生类的默认成员函数 5.继承与友元 6.继承与静态成员 7.菱形继承与菱形虚拟继承 8.总结 1.继承的概念及定义 1.1概念 继承: 它允许你创建一个新的类&#xff08;称为子类或派…