Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《Flutter FFI 数组》
- 《Flutter FFI 内存管理》
-
《Flutter FFI Dart Native API》
在前面的章节中,介绍了基础数据类型和函数的知识,在这一章节中,将介绍 Dart 与 C 语言的字符串传递方式。
1、C 语言返回字符串给 Dart
1.1 C 语言字符串
C语言中的字符串是以 “\0
” 为结束标记的,假设 C 语言中定义了一个 greetingString()
函数,返回了问候语:
#include <stdint.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API const char *greetString() {
return "Hello from Native\0";
}
那么,我们如何在 Dart 中使用该函数返回的字符串呢?
1.2 引入 ffi 库
在 Dart 中,可以引用 ffi
库来帮助我们快速实现字符串转换功能。
首先,打开 pubspec.yaml
文件,增加依赖库:
dependencies:
ffi: ^1.0.0
然后,执行 pub get
,下载依赖。此时便可以在 Dart 代码中引入 ffi 库:
import 'package:ffi/ffi.dart';
1.3 C字符串转Dart字符串
下面接下来看一下如何使用ffi
库把 C 的字符串映射到 Dart 中使用。
首先,在 Dart 中定义两个函数类型,用于映射 C 中的 greetingString()
函数:
typedef Native_greetingString = Pointer<Int8> Function();
typedef FFI_greetingString = Pointer<Int8> Function();
接着,在 Dat 中编写代码调用 C 函数,并将 C 的字符串转为 Dart 的字符串:
//加载符号
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//查找目标函数
FFI_greetingString greetingFunc = nativeApi
.lookupFunction<Native_greetingString, FFI_greetingString>("greetString");
//调用 greetString 函数,并将结果转为 Dart String.
Pointer<Int8> result = greetingFunc();
String greeting = result.cast<Utf8>().toDartString();
//打印结果
print("greeting=$greeting");
//输出结果
//greeting=Hello from Native
代码说明:
- C 语言中的字符串用
char*
表示,字符串是以 “\0
” 为结束标记; - Dart 需要使用
Pointer<Int8>
表示 C 语言中的char*
类型; - toDartString() 是
ffi
库提供的函数,用于将Pointer<Utf8>
转为 Dart String。因此需要将Pointer<Int8>
转为Pointer<Utf8>
类型。当然,也可以直接用Pointer<Utf8>
类型表示 C 中的字符串; -
greetString()
返回的是一个字符串常量,因此不需要释放内存。
toDartString()
的工作原理是把 cahr*
转为 char
数组,然后采用 utf8
进行解码。该函数定义如下:
/// Extension method for converting a`Pointer<Utf8>` to a [String].
extension Utf8Pointer on Pointer<Utf8> {
/// The number of UTF-8 code units in this zero-terminated UTF-8 string.
///
/// The UTF-8 code units of the strings are the non-zero code units up to the
/// first zero code unit.
int get length {
_ensureNotNullptr('length');
final codeUnits = cast<Uint8>();
return _length(codeUnits);
}
/// Converts this UTF-8 encoded string to a Dart string.
///
/// Decodes the UTF-8 code units of this zero-terminated byte array as
/// Unicode code points and creates a Dart string containing those code
/// points.
///
/// If [length] is provided, zero-termination is ignored and the result can
/// contain NUL characters.
///
/// If [length] is not provided, the returned string is the string up til
/// but not including the first NUL character.
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'.");
}
}
}
说明:
- 除了 Utf8 之外,
ffi
库还提供了 Utf16,有兴趣可以了解一下。
2、Dart 返回字符串给 C
上面介绍了如何把 C 的字符串转为 Dart 的字符串,接下来看看反过来是怎样实现的。
首先,在 C 中定义两个函数:
-
reverse_string()
用于反转字符串; -
free_string()
用于释放字符串所占用的内存;
#include <stdint.h>
#define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used))
DART_API char *reverse_string(const char *str, int length)
{
// Allocates native memory in C.
char *reversed_str = (char *)malloc((length + 1) * sizeof(char));
for (int i = 0; i < length; i++)
{
reversed_str[length - i - 1] = str[i];
}
reversed_str[length] = '\0';
return reversed_str;
}
void free_string(char *str)
{
// Free native memory in C which was allocated in C.
free(str);
}
然后,在 Dart 中定义与 C 相对应的函数类型:
typedef Native_reverseString = Pointer<Int8> Function(Pointer<Int8>, Int32);
typedef FFI_reverseString = Pointer<Int8> Function(Pointer<Int8>, int);
typedef Native_freeString = Void Function(Pointer<Int8>);
typedef FFI_freeString = void Function(Pointer<Int8>);
最后,在 Dart 调用 C 的函数,将 Dart 字符串转成 C 的字符串,并进行内存释放;
//加载库
DynamicLibrary nativeApi = Platform.isAndroid
? DynamicLibrary.open("libnative_ffi.so")
: DynamicLibrary.process();
//查找目标函数 - reverse_string
FFI_reverseString reverseFunc = nativeApi
.lookupFunction<Native_reverseString, FFI_reverseString>("reverse_string");
//查找目标函数 - free_string
FFI_freeString freeFunc = nativeApi
.lookupFunction<Native_freeString, FFI_freeString>("free_string");
//调用函数
String value = "ABCDEFG";
Pointer<Int8> nativeValue = value.toNativeUtf8().cast<Int8>();
Pointer<Int8> reverseValue = reverseFunc(nativeValue, value.length);
print("original.value=$value");
print("reverse.value=${reverseValue.cast<Utf8>().toDartString()}");
//释放字符串所占用的内存
freeFunc(nativeValue); //或者调用 calloc.free(nativeValue);
freeFunc(reverseValue); //或者调用 calloc.free(reverseValue);
//输出结果:
//original.value=ABCDEFG
//reverse.value=GFEDCBA
说明:
- 因为 C 中的
reverse_string()
使用malloc
分配了内存,因此在使用完之后,必须使用free
释放内存,否则会有内存泄漏; -
toNativeUtf8()
是由 ffi 库提供的API,调用该函数时会在 Native 中分配内存,因此使用完后也需要释放内存。
也可以使用calloc.free()
来释放由malloc
分配的内存;
toNativeUtf8()
函数的作用将 Dart 字符串转为 Pointer<Utf8>
,以便在 C 中使用 Dart 字符串。
toNativeUtf8()
其工作原理是把 Dart 字符串转为 Utf8,然后在 C 中分配一个 char 数组,接着再复制 Utf8 的内存数据。该函数的定义如下:
/// Extension method for converting a [String] to a `Pointer<Utf8>`.
extension StringUtf8Pointer on String {
/// Creates a zero-terminated [Utf8] code-unit array from this String.
///
/// If this [String] contains NUL characters, converting it back to a string
/// using [Utf8Pointer.toDartString] will truncate the result if a length is
/// not passed.
///
/// Unpaired surrogate code points in this [String] will be encoded as
/// replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD) in
/// the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
///
/// Returns an [allocator]-allocated pointer to the result.
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();
}
}
说明:
- 同样的,ffi 库也提供了 Utf16 的
toNativeUtf16
,有兴趣可以了解一下。
3、总结
上面介绍 C 字符串与 Dart 字符串的相互转换,还提到了内存的分配和释放。在后面的章节中,将会介绍结构体、数组、内存管理等知识,欢迎关注。