从这篇文章开始将一起来研究 Dart 2.5 之后推出一个新的特性,就是 Dart 与 C(或 C++)的互操作。这就意味着在 Dart 可以做一些类似 JNI 操作。
Dart 与 C 的互操作主要是借助 dart:ffi 库,ffi 全称为 foreign function interface(外部函数接口)。目前 dart:ffi 库还是处于 beta 中,不建议现在直接投入生产环境中,可能后续的 API 有所变动。
此外目前,直接从 Dart 调用 C 的支持仅限于使用原生扩展深入集成到 Dart VM 中,然而在 Flutter 中调用 C 语言,目前只能通过使用 Platform Channel 调用主机,可以在那选择调用 C 或 C++。
Dart-C 互操作支持的两种主要方案:
- 在宿主机操作系统(OS)上调用基于 C/C++ 的系统 API 或者直接通过 dart:ffi API 在 OS 开辟内存
- 为单个操作系统或跨平台调用基于 C/C++ 的库
1. 为什么需要支持 Dart 与 C 互操作
我们都知道,C/C++ 语言依然是目前语言排行榜上的王者,依然在很多领域中广泛应用。尽管现在很多现代语言遍地开花,但是这些现代语言多多少少都会支持与 C/C++ 互操作的特性。这是因为有一些很特殊场景依然需要 C/C++ 来做底层的支持。比如开发常用的场景调用操作系统底层实现、加解密算法的实现、音视频的处理、图形图像的处理等。
因为目前很多图形图像、音视频的跨平台库(比如 openGL)大多数都是 C/C++的库,如果说 Dart 提供调用 C/C++库的能力,也就意味着 Dart 在很多领域的库使用将更有可能。此外相对于现代语言更加高效以及安全性更高,所以 Dart 支持 C/C++互操作是非常有必要的。
image.png先来看下简单 hello_dart 调用 C 语言的例子。
1.1 C 语言编译器环境准备
关于 C 语言编译器有 GCC 和 Clang 等,首先需要检查是否已经安装了 GCC 编译器或者 Clang(由于当前操作系统是 macOS,安装了 Xcode 所以默认配置了 Clang):
image.png注意:Windows 平台用户可以去这个地址
下载 GCC,注意下载完后记得配置相应环境变量即可。最后通过 gcc -v
检查当前 GCC 版本是否正常。
对于 Mac 平台用户可以使用 homebrew 来安装 GCC:
brew install gcc
安装比较简单这就不一一展开了,这里就用自带的 Clang 来做为编译器。
1.2 编写 C 语言源代码
#include<stdio.h>
void helloDart() {
printf("hello dart, this is print from C");
}
1.3 利用 Clang 编译器将 C 语言源码分别编译成 hello_dart.dll(Windows 平台动态链接库)和 hello_dart.dylib(Mac 平台的动态库)
# Clang 编译成 Windows 平台的 .dll 动态库
clang hello_dart.c -shared -o hello_dart.dll
# Clang 编译成 Mac 平台的 .dylib 动态库
clang -dynamiclib hello_dart.c -o hello_dart.dylib
# Clang 编程成 Linux 平台的 .so 动态库
clang -shared -fPIC -o hello_dart.so hello_dart.c
###############################
# GCC 编译成 Windows 平台的 .dll 动态库
gcc hello_dart.c -shared -o hello_dart.dll
# GCC 编译成 Mac 平台的 .dylib 动态库
gcc -dynamiclib hello_dart.c -o hello_dart.dylib
# GCC 编程成 Linux 平台的 .so 动态库
gcc -shared -fPIC -o hello_dart.so hello_dart.c
然后会看到相应目录下会生成两个 lib 库:
image.png1.4 编写 Dart 调用 C 库代码
import 'dart:ffi' as ffi;//引入外部函数接口库 dart:ffi
import 'dart:io';
//定义 Native(C/C++)和 Dart 层函数签名(函数签名,就是对一个方法或函数的描述,包括返回值类型,形参类型)
typedef NativeHelloDartSign = ffi.Void Function();//定义 Native(C/C++)函数签名,由于 helloDart 方法返回值为 void 且没有参数,所以是 ffi.Void Function()
typedef DartHelloDartSign = void Function();//定义 Dart 函数签名,由于 helloDart 方法返回值为 void 且没有参数,所以是 void Function()
void main() {
ffi.DynamicLibrary dynamicLib;
if (Platform.isWindows) {//如果是 windows 平台加载.dll
dynamicLib = ffi.DynamicLibrary.open("/Users/gitchat/clib/hello_dart.dll");
} else if (Platform.isMacOS) {//如果是 Mac 平台加载.dylib
dynamicLib = ffi.DynamicLibrary.open("/Users/gitchat/clib/hello_dart.dylib");
} else if (Platform.isLinux) {//如果是 Linux 平台加载.so
dynamicLib = ffi.DynamicLibrary.open("/Users/gitchat/clib/hello_dart.so");
}
if (dynamicLib != null) {
//lookupFunction 就是去动态库中查找指定的函数以及将 Native 类型的 C 函数转化为 Dart 的 Function 类型
var helloDart = dynamicLib.lookupFunction<NativeHelloDartSign, DartHelloDartSign>("helloDart");
//最后 Dart 层调用方法
helloDart();
}
}
输出结果:
image.png2. dart:ffi 库简单介绍
关于 dart:ffi 库源码其实非常的简单就是仅仅 5 个类:
image.png2.1 ffi 库映射的 NativeType
NativeType 是用于描述 C 中数据类型的抽象类,由它派生出来了很多子类用于描述 C 中不同的数据类型,从而可以完整描述所有 C 中类型信息。
/// [NativeType]'s subtypes represent a native type in C.
///
/// [NativeType]'s subtypes are not constructible in the Dart code and serve
/// purely as markers in type signatures.
abstract class NativeType {
const NativeType();
}
为了更清晰了解 NativeType 这个类,用一张简单 UML 类图描述下它和子类型之间的关系:
image.png使用 Dart 调用 C 语言中 API,其中无疑需要处理一点就是类型的映射和统一,也就是怎么在 Dart 中可以使用到 C 中的一些数据类型。所以 NativeType 这个类就是映射了 C 语言中的一些基本数据类型。
描述 C 语言层 NativeType 的子类型 | 含义 | 映射对应 Dart 层的数据类型 |
---|---|---|
Int8 | 表示 C 中有符号位 8 位整型 | int |
Int16 | 表示 C 中有符号位 16 位整型 | int |
Int32 | 表示 C 中有符号位 32 位整型 | int |
Int64 | 表示 C 中有符号位 64 位整型 | int |
Unit8 | 表示 C 中无符号位 8 位整型 | int |
Unit16 | 表示 C 中无符号位 16 位整型 | int |
Unit32 | 表示 C 中无符号位 32 位整型 | int |
Unit64 | 表示 C 中无符号位 64 位整型 | int |
IntPtr | 表示 C 中指针大小整型 | int |
Double | 表示 C 中 64 位的 double 双精度类型 | double |
Float | 表示 C 中 32 位的 float 单精度类型 | double |
Pointer | 表示指向 C 中内存一个指针,一般表示引用类型 | Pointer |
NativeFunction | 表示 C 中的函数类型 | Function |
Void | 表示 C 中的 void 空类型 | void |
Pointer<Utf8> | 表示 C 中的字符串类型(char *) | Pointer<Utf8> |
上面的类型映射表非常重要,因为在定义 C(Native)层函数签名和 Dart 层函数签名,包括返回值类型,参数类型都需要一一按照上述表中进行映射。比如上述例子中的 NativeHelloDartSign 返回值类型 Void 就是映射对应了 DartHelloDartSign 中的 void 类型。
typedef NativeHelloDartSign = ffi.Void Function();//定义 Native(C/C++)函数签名,由于 helloDart 方法返回值为 void 且没有参数,所以是 ffi.Void Function()
typedef DartHelloDartSign = void Function();//定义 Dart 函数签名,由于 helloDart 方法返回值为 void 且没有参数,所以是 void Function()
比如说我们现在要调用 C 中一个两数求和函数,参数和返回值类型都是 int,C 中函数实现如下:
//sum.c
#include <stdio.h>
long sum(short a, int b) {
return a + b;
}
int main() {
printf("%ld", sum(23, 100000000));
return 0;
}
那么对上面 C 中函数,如何通过 Dart 层映射实现对应函数签名呢?实际上很简单只需要参考类型映射表,short 在 C 中一般占用 16 位所以 Native 函数签名对应类型是 Int16,long 在 C 中一般占用 64 位所以 Native 函数签名对应类型是 Int64,所以对应实现的 Dart 代码如下:
import 'dart:ffi' as ffi;
//ffi.Int64 对应 C 中的 long 类型(64 位整型),ffi.Int16 对应 C 中的 short 类型(16 位整型),ffi.Int32 对应 C 中的 int 类型(32 位整型)
typedef NativeSumSign = ffi.Int64 Function(ffi.Int16, ffi.Int32);//对应 C 中的 sum 函数,long sum (short a, int b)
//根据类型映射表,ffi.Int64、ffi.Int16、ffi.Int32 对应到 dart 中都是 int 类型
typedef DartSumSign = int Function(int, int);
void main() {
ffi.DynamicLibrary dynamicLib = ffi.DynamicLibrary.open(
"/Users/gitchat/clib/sum.dylib");
if (dynamicLib != null) {
var sum = dynamicLib.lookupFunction<NativeSumSign, DartSumSign>("sum");
print(sum(30, 1000000000));
}
}
输出结果:
image.png由于字符串可是开发中最经常接触类型,但是关于 C 中字符串类型(char *)使用 Pointer<Utf8> 来表示,这里有个简单例子说明下字符串类型映射关系。
//C 中实现
#include <stdio.h>
void printName(char *name) {
printf("%s", name);
}
然后将 C 源码打包成 .dylib 动态库,然后使用 Dart 去加载动态并调用相应的函数,具体 Dart 实现如下:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
//void printName(char *name)
typedef NativePrintNameSign = Void Function(Pointer<Utf8> name);//注意:对于 C 中字符串类型使用 NativeType 子类型 Pointer<Utf8>表示
typedef DartPrintNameSign = void Function(Pointer<Utf8> name);//Dart 对于 Pointer 的映射和 NativeType 一样
void main() {
var dylib = DynamicLibrary.open("/Users/gitchat/clib/string.dylib");
if(dylib != null) {
var printName = dylib.lookupFunction<NativePrintNameSign, DartPrintNameSign>("printName");
printName(Utf8.toUtf8("this is string input from dart..."));//传递字符串到 C 中使用 Utf8 包装器,如果需要拿到 C 中字符串则需要使用 Utf8.fromUtf8 转化成字符串输出
}
}
输出结果:
image.png
网友评论