准备macos的动态库
- 使用Xcode创建动态库
- File - New - Project - macOS - Library - Tpye: Dynamic
- 输入动态库名字 点击下一步即可
- 使用C语言编写,不能有OC的任何代码
- 动态库如下 test 是你输入的名字,前面的lib是系统自动加的。
编写c语言代码
- 头文件代码
- c代码
编译动态库
- 使用Xcode编译动态库代码
- 然后导出动态库文件准备着
- Copy动态库文件到
/Users/apple/Library/Containers/com.example.xxx/Data/ apple换成你电脑的用户名com.example.xxx是你应用的包名(可以打开macos的工程去查看)- 最终的文件路径为
/Users/apple/Library/Containers/com.example.xxx/Data/libtest.dylib - 为什么把动态库Copy到这个目录?
- 因为你的Mac程序在不做任何配置的情况下,能访问到这个路径。放到电脑的其他路径可能没有运行权限。
准备flutter项目
- 创建Flutter项目
flutter create demo
- 我的Flutter版本为
Flutter 3.3.5 Tools • Dart 2.18.2 • DevTools 2.15.0
编写dart代码
- 打开动态库
import 'dart:ffi' as ffi;
var libraryPath = "/Users/apple/Library/Containers/com.example.xxx/Data/libtest.dylib";
final dylib = ffi.DynamicLibrary.open(libraryPath);
- 为 C 函数的 FFI 类型签名的定义一个类型 和 调用 C 函数的变量定义一个类型
// 同步函数
typedef SumFunc = Int32 Function(Int32 a, Int32 b);
typedef Sum = int Function(int a, int b);
// 异步函数 带有callback回调
typedef DartCallback = ffi.Void Function(ffi.Int,ffi.Pointer<ffi.Char>);
typedef CallbackNative = Int Function(Pointer<NativeFunction<DartCallback>>);
typedef CallbackSandbox = int Function(Pointer<NativeFunction<DartCallback>>);
参考资料:dart.cn/guides/libr…
- 调用动态库的代码
同步函数调用
final sumPointer = dylib.lookup<NativeFunction<SumFunc>>('sum'); // 这里的sum是C语言动态库的函数名字
final sum = sumPointer.asFunction<Sum>();
print('3 + 5 = ${sum(3, 5)}');
异步函数调用
CallbackSandbox callback = dylib.lookupFunction<CallbackNative, CallbackSandbox>('hellCallback');
final value = callback(Pointer.fromFunction(_callback)); // 回调函数看下面的说明
print("value = $value");
回调函数定义
- 要求回调函数为
静态函数或者全局函数 - 不可以直接定义到
class中
void _callback(int a, Pointer<ffi.Char> msg) {
print("--------");
print("$a - $b - ${msg.toDartString()}");
;
}
- 整体的代码
import 'dart:ffi' as ffi;
import 'dart:ffi';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as path;
typedef SumFunc = Int32 Function(Int32 a, Int32 b);
typedef Sum = int Function(int a, int b);
typedef DartCallback = ffi.Void Function(ffi.Int, ffi.Int, ffi.Pointer<ffi.Char>);
typedef CallbackNative = Int Function(Pointer<NativeFunction<DartCallback>>);
typedef CallbackSandbox = int Function(Pointer<NativeFunction<DartCallback>>);
// 静态函数或者全局函数
void _callback(int a, int b, Pointer<ffi.Char> msg) {
print("--------");
print("$a - $b - ${msg.toDartString()}");
;
}
class Setting extends StatefulWidget {
Setting({Key? key}) : super(key: key);
@override
_SettingState createState() => _SettingState();
}
class _SettingState extends State<Setting> {
void _ffiButtonClick() {
if (Platform.isMacOS) {
var libraryPath = path.join(Directory.current.path, 'libMacBoxSdk.dylib');
final dylib = ffi.DynamicLibrary.open(libraryPath);
final sumPointer = dylib.lookup<NativeFunction<SumFunc>>('sum');
final sum = sumPointer.asFunction<Sum>();
print('3 + 5 = ${sum(3, 5)}');
}
}
void _ffiAsyncButtonClick() {
var libraryPath = path.join(Directory.current.path, 'libMacBoxSdk.dylib');
final dylib = ffi.DynamicLibrary.open(libraryPath);
CallbackSandbox callback = dylib.lookupFunction<CallbackNative, CallbackSandbox>('hellCallback');
final value = callback(Pointer.fromFunction(_callback));
print("value = $value");
}
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 10,
children: [
ElevatedButton(
child: Text('ffi同步通信'),
onPressed: _ffiButtonClick,
),
ElevatedButton(
child: Text('ffi异步通信'),
onPressed: _ffiAsyncButtonClick,
)
],
);
}
}
进阶
动态库位置优化
- 按照下图操作
动态库位置更改后的加载方式
- 先open整个生命周期可以只执行一次
final dylib = ffi.DynamicLibrary.open('libMacBoxSdk.dylib');
- 再调用 open 调用之后下面调用才会生效
print('5 + 5 = ${mboxSdk.sum(5, 5)}');
ffigen 使用
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
ffigen: ^7.2.0
ffigen:
output: 'libtest_generated_bindings.dart' // 输出路径
name: 'test'
description: 'Holds bindings to test.'
headers:
entry-points:
- 'lib/library/include/test.h' // 输入路径
include-directives:
- '**test.h'
参考链接:pub.dev/packages/ff…
ffigen 代码
import 'utf8.dart';
import 'libtest_generated_bindings.dart' as sdk;
void _ffigenButtonClick() {
var libraryPath = path.join(Directory.current.path, 'libtest.dylib');
final test = sdk.test(ffi.DynamicLibrary.open(libraryPath));
print('5 + 5 = ${test.sum(5, 5)}');
}
void _ffigenAsyncButtonClick() {
var libraryPath = path.join(Directory.current.path, 'libtest.dylib');
final mboxSdk = sdk.MacBoxSdk(DynamicLibrary.open(libraryPath));
final value = mboxSdk.hellCallback(Pointer.fromFunction(_callback));
print('value = $value');
}
// 静态函数或者全局函数
void _callback(int a, int b, Pointer<ffi.Char> msg) {
print("--------");
print("$a - $b - ${msg.toDartString()}"); // 这里的toDartString()参考utf8.dart核心代码
;
}
utf8.dart核心代码
- 主要处理 toDartString() 函数
- C语言
char *指针转Dart字符串
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
extension Utf8Pointer on Pointer<Char> {
int get length {
_ensureNotNullptr('length');
final codeUnits = cast<Uint8>();
return _length(codeUnits);
}
String toDartString({int? length}) {
_ensureNotNullptr('toDartString');
final codeUnits = cast<Uint8>();
if (length != null) {
RangeError.checkNotNegative(length, 'length');
} else {
length = _length(codeUnits);
}
return utf8.decode(codeUnits.asTypedList(length));
}
static int _length(Pointer<Uint8> codeUnits) {
var length = 0;
while (codeUnits[length] != 0) {
length++;
}
return length;
}
void _ensureNotNullptr(String operation) {
if (this == nullptr) {
throw UnsupportedError("Operation '$operation' not allowed on a 'nullptr'.");
}
}
}
extension StringUtf8Pointer on String {
Pointer<Utf8> toNativeUtf8({Allocator allocator = malloc}) {
final units = utf8.encode(this);
final Pointer<Uint8> result = allocator<Uint8>(units.length + 1);
final Uint8List nativeString = result.asTypedList(units.length + 1);
nativeString.setAll(0, units);
nativeString[units.length] = 0;
return result.cast();
}
}