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

news2024/9/28 13:25:35

开发环境

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.so
        native_add

        # 动态库使用:SHARED、静态库使用:STATIC
        SHARED

        # 源文件可以放在别的地方
        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.coloradmin.cn/o/1078199.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【考研408真题】2022年408数据结构41题---判断当前顺序存储结构树是否是二叉搜索树

文章目录 思路408考研各数据结构C/C代码&#xff08;Continually updating&#xff09; 思路 很明显&#xff0c;这是一个顺序存储结构的树的构成方法。其中树的根节点位置从索引0开始&#xff0c;对于该结构&#xff0c;存在有&#xff1a;如果当前根节点的下标为n&#xff0c…

波奇学C++:哈希

哈希本质是的值和位置建立关联起来&#xff0c;这种关联关系就是哈希函数 示例:除留余数&#xff1a;对输入的数字取模。 哈希冲突&#xff1a;多个不同的值指向同一个位置 解决方法&#xff1a; 闭散列&#xff1a;开发地址法。 把24放在下一个位置 哈希桶 闭散列法 闭散列…

NNDL:作业3

在Softmax回归的风险函数(公式(3.39))中如果加上正则化项会有什么影响? (1) 在 Softmax 回归的风险函数中加入正则化项会对模型的训练产生影响。正则化项的作用是对模型的复杂度进行惩罚&#xff0c;防止过拟合的发生。 (2) 原书公式为&#xff1a; 在加入正则化后损失函数…

STM32单片机入门学习(六)-光敏传感器控制LED

光敏传感器模块和LED接线 LED负极接B12,正极接VCC 光敏传感模块一DO端接B13,GND接GND&#xff0c;VCC接VCC,AO不接。 如图&#xff1a; 主程序代码&#xff1a;main.c #include "stm32f10x.h" #include "Delay.h" //delay函数所在头文件 #include …

Python中套接字实现服务端和客户端3-3

3 创建客户端的步骤 创建客户端的步骤如图5所示。 图5 创建客户端的步骤 从图5可以看出&#xff0c;对于客户端来说&#xff0c;首先创建套接字&#xff0c;之后通过创建的套接字去连接服务端&#xff0c;如果连接成功&#xff0c;则继续通过该套接字向服务端发送数据&#x…

扩展windows 10 文件夹文件路径位数

Enable Long Paths in Windows 10, Version 1607, and Later PowerShell Copy New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force 管理员权限运行 PowerShell…

【开源电商网站】(2),使用docker-compose和dockerfile进行配置,设置自定义的镜像,安装插件,增加汉化包,支持中文界面汉化。

项目相关代代码地址 相关内容&#xff1a; https://blog.csdn.net/freewebsys/category_12461196.html 原文地址&#xff1a; https://blog.csdn.net/freewebsys/article/details/133666433 包括以下运行的详细代码&#xff1a; https://gitee.com/study-demo-all/oscommerc…

将cpu版本的pytorch换成gpu版本

1.首先激活虚拟环境 winRcmd 打开dos命令窗口 查看虚拟环境列表 conda env list 激活虚拟环境 2.将原来的pytorch_cpu版本换成gpu版本 注意&#xff1a;安装gpu版本的pytorch时并不需要先卸载原来的cpu版本pytorch,安装时会自己替换的 打开pytorch官网查看以前版本 Previo…

vant组件安装之后导入所有组件报错

问题&#xff1a;引入vant报错(Module build failed (from ./node_modules/postcss-loader/src/index.js): 解决办法&#xff1a;在node_modules里面找到 vant目录下面的lib里面的 index.css&#xff1b; 在url前面加上空格即可

MongoDB 笔记

1 insert 、create、save区别 insert: 主键不存在则正常插入&#xff1b;主键已存在&#xff0c;抛出DuplicateKeyException 异常 save: 主键不存在则正常插入&#xff1b;主键已存在则更新 insertMany&#xff1a;批量插入&#xff0c;等同于批量执行 insert create&#x…

地下城堡3最强英雄搭配,地下城堡3公认最强阵容

在地下城堡3中&#xff0c;组建一支最强的阵容是玩家们追求的目标之一。通过合理的角色搭配和战术配合&#xff0c;你可以打造一个无敌的团队&#xff0c;在战斗中几乎无往而不胜。下面是地下城堡3公认最强阵容搭配攻略&#xff0c;让你在游戏中轻松征战各个副本和挑战。 关注【…

基于 LSTM 进行多类文本分类(附源码)

NLP 的许多创新是如何将上下文添加到词向量中。一种常见的方法是使用循环神经网络。以下是循环神经网络的概念&#xff1a; 他们利用顺序信息。 他们可以捕捉到到目前为止已经计算过的内容&#xff0c;即&#xff1a;我最后说的内容会影响我接下来要说的内容。 RNNs 是文本和…

一次性读懂Mendix的库间“数据同步”功能

Data sync&#xff0c;对于那些深谙其道的技术高手而言&#xff0c;意义不言自明。然鹅对整天在村工厂里打螺丝的我来说&#xff0c;却经历了一段难捱的时期。时至今日&#xff0c;我仍然时不时地选择性地遗忘某些概念和技术点。因此&#xff0c;本文章记录我之前一点实操的心得…

【测试】robotframework安装

目录 python安装 pip一系列安装 运行效果 参考文档 python安装 注意管理员权限安装&#xff0c;不然2503的错误 。 pip一系列安装 pip install robotframework pip install wxPython pip install robotframework-ride 运行python ride.py pip install setuptools 解决…

125KHz低频接收唤醒芯片:Si3933(TSSOP16)

Si3933 具有内部时钟产生器&#xff0c;可使用晶体振荡器或者RC振荡器&#xff0c;也可以使用外部时钟。 Si3933 是一款三通道的D功耗ASK接 收机&#xff0c;可用于检测15KHz-150KHz低频载波频率的数字信号&#xff0c;并产生唤醒信号。内部集成的校验器用于检测 16 位或 32 位…

双态IT乌镇大会 | 首批《数据中心业务连续性等级评价准则》试点单位将诞生

2023年10月13日-15日&#xff0c;由ITSS分会、证券基金行业信息技术应用创新联盟指导&#xff0c;ITSS数据中心运营管理组&#xff08;DCMG&#xff09;、双态IT论坛、智能运维国标工作组主办&#xff0c;ITSS媒体组、AI范儿协办的“2023第六届双态IT乌镇用户大会”将于浙江乌镇…

SpringBoot采用Dynamic-Datasource方式实现多JDBC数据源

目录 1. Dynamic-Datasource实现多JDBC数据源配置1.1 特性1.2 Mysql数据准备2.2 通过Dynamic-Datasource实现多JDBC数据源2.2.1 pom.xml依赖 2.2.2 application.properties配置2.2.3 使用DS注解选择DataSource2.2.4 使用Transactional DSTransactional实现事务 2.3 动态数据源…

下一代架构设计:云原生、容器和微前端的综合应用

文章目录 云原生&#xff1a;构建可弹性扩展的应用1. 微服务架构2. 容器化3. 自动化和自动扩展 容器化和云原生的结合1. 一致性和可移植性2. 弹性和可伸缩性3. 快速部署和更新4. 资源利用率 微前端&#xff1a;前端架构的演进1. 微前端应用2. 统一的外壳应用3. 独立部署 云原生…

TikTok在跨境电商中的作用:挖掘潜在客户的最佳途径

​随着全球数字化浪潮的不断发展&#xff0c;跨境电商行业也经历了巨大的变革。传统的市场营销渠道已经不再足够&#xff0c;企业们需要不断探寻新的方法来吸引潜在客户。在这个过程中&#xff0c;社交媒体平台TikTok逐渐崭露头角&#xff0c;成为了吸引潜在客户的一个选择。本…

[PwnThyBytes 2019]Baby_SQL - 代码审计+布尔盲注+SESSION_UPLOAD_PROGRESS利用

[PwnThyBytes 2019]Baby_SQL 1 解题流程1.1 分析1.2 解题 2 思考总结 1 解题流程 1.1 分析 此题参考文章&#xff1a;浅谈 SESSION_UPLOAD_PROGRESS 的利用 访问正常来讲用ctf-wscan是能扫出source.zip文件的&#xff0c;且F12后提示了有source.zip&#xff0c;那我们就下载…