? Flutter FFI 学习笔记系列
- 《Flutter FFI 最简示例》
- 《Flutter FFI 基础数据类型》
- 《Flutter FFI 函数》
- 《Flutter FFI 字符串》
- 《Flutter FFI 结构体》
- 《Flutter FFI 类》
- 《FlutterFFI 数组》
- 《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");
Pointer<Int8> result = greetingFunc();
String greeting = result.cast<Utf8>().toDartString();
print("greeting=$greeting");
? 代码说明:
- 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 Utf8Pointer on Pointer<Utf8> {
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'.");
}
}
}
? 说明:
- 除了 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)
{
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(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();
FFI_reverseString reverseFunc = nativeApi
.lookupFunction<Native_reverseString, FFI_reverseString>("reverse_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);
freeFunc(reverseValue);
说明:
- 因为 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 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();
}
}
? 说明:
- 同样的,ffi 库也提供了 Utf16 的
toNativeUtf16 ,有兴趣可以了解一下。
?
3、总结
? ? 上面介绍 C 字符串与 Dart 字符串的相互转换,还提到了内存的分配和释放。在后面的章节中,将会介绍结构体、数组、内存管理等知识,欢迎关注。 ? ? ?
|