美文网首页
Dart 与 C 的互相调用

Dart 与 C 的互相调用

作者: you的日常 | 来源:发表于2021-01-04 12:20 被阅读0次

从这篇文章开始将一起来研究 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++ 的库
image.png

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 平台用户可以去这个地址

https://sourceforge.net/projects/mingw-w64/files/

下载 GCC,注意下载完后记得配置相应环境变量即可。最后通过 gcc -v 检查当前 GCC 版本是否正常。

image.png

对于 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.png

1.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.png

2. dart:ffi 库简单介绍

关于 dart:ffi 库源码其实非常的简单就是仅仅 5 个类:

image.png

2.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

2.2 ffi 库中的 DynamicLibrary 类使用

相关文章

网友评论

      本文标题:Dart 与 C 的互相调用

      本文链接:https://www.haomeiwen.com/subject/cdaebktx.html