Flutter FFI 学习笔记系列

  1. 《Flutter FFI 最简示例》
  2. 《Flutter FFI 基础数据类型》
  3. 《Flutter FFI 函数》
  4. 《Flutter FFI 字符串》
  5. 《Flutter FFI 结构体》
  6. 《Flutter FFI 类》
  7. 《Flutter FFI 数组》
  8. 《Flutter FFI 内存管理》
  9. 《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 库也提供了 Utf16toNativeUtf16,有兴趣可以了解一下。

 

3、总结

  
  上面介绍 C 字符串与 Dart 字符串的相互转换,还提到了内存的分配和释放。在后面的章节中,将会介绍结构体、数组、内存管理等知识,欢迎关注。