Dart 调用C语言
本篇博客研究Dart语言如何调用C语言代码混合编程,最后我们实现一个简单示例,在C语言中编写简单加解密函数,使用dart调用并传入字符串,返回加密结果,调用解密函数,恢复字符串内容。

环境准备
编译器环境
如未安装过VS编译器,则推荐使用GCC编译器,下载一个64位Windows版本的GCC——MinGW-W64
下载地址

如上,它有两个版本,sjlj和seh后缀表示异常处理模式,seh 性能较好,但不支持 32位。 sjlj 稳定性好,可支持 32位,推荐下载seh 版本

将编译器安装到指定的目录,完成安装后,还需要配置一下环境变量,将安装目录下的bin目录加入到系统Path环境变量中,bin目录下包含gcc.exe、make.exe等工具链。

测试环境
配置完成后,检测一下环境是否搭建成功,打开cmd命令行,输入gcc -v能查看版本号则成功。

Dart SDK环境
去往Dart 官网下载最新的2.3 版本SDK,注意,旧版本不支持ffi 下载地址

下载安装后,同样需要配置环境变量,将dart-sdk\bin配置到系统Path环境变量中。

测试Dart ffi接口
简单示例
创建测试工程,打开cmd命令行

mkdir ffi-proj
cd ffi-proj
mkdir bin src
1
2
3
创建工程目录ffi-proj,在其下创建bin、src文件夹,在bin中创建main.dart文件,在src中创建test.c文件

编写test.c
我们在其中包含了windows头文件,用于showBox函数,调用Win32 API,创建一个对话框

#include<windows.h>

int add(int a, int b){
return a + b;
}

void showBox(){
MessageBox(NULL,"Hello Dart","Title",MB_OK);
}
1
2
3
4
5
6
7
8
9
10
进入src目录下,使用gcc编译器,将C语言代码编译为dll动态库

gcc test.c -shared -o test.dll
1
编写main.dart

import 'dart:ffi' as ffi;
import 'dart:io' show Platform;

/// 根据C中的函数来定义方法签名(所谓方法签名,就是对一个方法或函数的描述,包括返回值类型,形参类型)
/// 这里需要定义两个方法签名,一个是C语言中的,一个是转换为Dart之后的
typedef NativeAddSign = ffi.Int32 Function(ffi.Int32,ffi.Int32);
typedef DartAddSign = int Function(int, int);

/// showBox函数方法签名
typedef NativeShowSign = ffi.Void Function();
typedef DartShowSign = void Function();

void main(List<String> args) {
if (Platform.isWindows) {
// 加载dll动态库
ffi.DynamicLibrary dl = ffi.DynamicLibrary.open("../src/test.dll");

// lookupFunction有两个作用,1、去动态库中查找指定的函数;2、将Native类型的C函数转化为Dart的Function类型
var add = dl.lookupFunction<NativeAddSign, DartAddSign>("add");
var showBox = dl.lookupFunction<NativeShowSign, DartShowSign>("showBox");

// 调用add函数
print(add(8, 9));
// 调用showBox函数
showBox();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

深入用法
这里写一个稍微深入一点的示例,我们在C语言中写一个简单加密算法,然后使用dart调用C函数加密解密

编写encrypt_test.c,这里写一个最简单的异或加密算法,可以看到加密和解密实际上是一样的

#include <string.h>

#define KEY 'abc'

void encrypt(char *str, char *r, int r_len){
int len = strlen(str);
for(int i = 0; i < len && i < r_len; i++){
r[i] = str[i] ^ KEY;
}

if (r_len > len) r[len] = '\0';
else r[r_len] = '\0';

}

void decrypt(char *str, char *r, int r_len){
int len = strlen(str);
for(int i = 0; i < len && i < r_len; i++){
r[i] = str[i] ^ KEY;
}

if (r_len > len) r[len] = '\0';
else r[r_len] = '\0';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
编译为动态库

gcc encrypt_test.c -shared -o encrypt_test.dll
1
编写main.dart

import 'dart:ffi';
import 'dart:io' show Platform;
import "dart:convert";

/// encrypt函数方法签名,注意,这里encrypt和decrypt的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同
typedef NativeEncrypt = Void Function(CString,CString,Int32);
typedef DartEncrypt = void Function(CString,CString,int);

void main(List<String> args) {
if (Platform.isWindows) {
// 加载dll动态库
DynamicLibrary dl = DynamicLibrary.open("../src/encrypt_test.dll");
var encrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("encrypt");
var decrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("decrypt");

CString data = CString.allocate("helloworld");
CString enResult = CString.malloc(100);
encrypt(data,enResult,100);
print(CString.fromUtf8(enResult));

print("-------------------------");

CString deResult = CString.malloc(100);
decrypt(enResult,deResult,100);
print(CString.fromUtf8(deResult));
}
}

/// 创建一个类继承Pointer<Int8>指针,用于处理C语言字符串和Dart字符串的映射
class CString extends Pointer<Int8> {

/// 申请内存空间,将Dart字符串转为C语言字符串
factory CString.allocate(String dartStr) {
List<int> units = Utf8Encoder().convert(dartStr);
Pointer<Int8> str = allocate(count: units.length + 1);
for (int i = 0; i < units.length; ++i) {
str.elementAt(i).store(units[i]);
}
str.elementAt(units.length).store(0);

return str.cast();
}

// 申请指定大小的堆内存空间
factory CString.malloc(int size) {
Pointer<Int8> str = allocate(count: size);
return str.cast();
}

/// 将C语言中的字符串转为Dart中的字符串
static String fromUtf8(CString str) {
if (str == null) return null;
int len = 0;
while (str.elementAt(++len).load<int>() != 0);
List<int> units = List(len);
for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load();
return Utf8Decoder().convert(units);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
运行结果

可以看到将"helloworld"字符串加密后变成一串乱码,解密字符串后,恢复内容

完善代码
上述代码虽然实现了我们的目标,但是存在明显的内存泄露,我们使用CString 的allocate和malloc申请了堆内存,但是却没有手动释放,这样运行一段时间后可能会耗尽内存空间,手动管理内存往往是C/C++中最容易出问题的地方,这里我们只能进行一个简单的设计来回收内存

/// 创建Reference 类来跟踪CString申请的内存
class Reference {
final List<Pointer<Void>> _allocations = [];

T ref<T extends Pointer>(T ptr) {
_allocations.add(ptr.cast());
return ptr;
}

// 使用完后手动释放内存
void finalize() {
for (final ptr in _allocations) {
ptr.free();
}
_allocations.clear();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修改代码

import 'dart:ffi';
import 'dart:io' show Platform;
import "dart:convert";

/// encrypt函数方法签名,注意,这里encrypt和decrypt的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同
typedef NativeEncrypt = Void Function(CString,CString,Int32);
typedef DartEncrypt = void Function(CString,CString,int);

void main(List<String> args) {
if (Platform.isWindows) {
// 加载dll动态库
DynamicLibrary dl = DynamicLibrary.open("../src/hello.dll");
var encrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("encrypt");
var decrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("decrypt");

// 创建Reference 跟踪CString
Reference ref = Reference();

CString data = CString.allocate("helloworld",ref);
CString enResult = CString.malloc(100,ref);
encrypt(data,enResult,100);
print(CString.fromUtf8(enResult));

print("-------------------------");

CString deResult = CString.malloc(100,ref);
decrypt(enResult,deResult,100);
print(CString.fromUtf8(deResult));

// 用完后手动释放
ref.finalize();
}
}

class CString extends Pointer<Int8> {

/// 开辟内存控件,将Dart字符串转为C语言字符串
factory CString.allocate(String dartStr, [Reference ref]) {
List<int> units = Utf8Encoder().convert(dartStr);
Pointer<Int8> str = allocate(count: units.length + 1);
for (int i = 0; i < units.length; ++i) {
str.elementAt(i).store(units[i]);
}
str.elementAt(units.length).store(0);

ref?.ref(str);
return str.cast();
}

factory CString.malloc(int size, [Reference ref]) {
Pointer<Int8> str = allocate(count: size);
ref?.ref(str);
return str.cast();
}

/// 将C语言中的字符串转为Dart中的字符串
static String fromUtf8(CString str) {
if (str == null) return null;
int len = 0;
while (str.elementAt(++len).load<int>() != 0);
List<int> units = List(len);
for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load();
return Utf8Decoder().convert(units);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
总结
dart:ffi包目前正处理开发中,暂时释放的只有基础功能,且使用dart:ffi包后,Dart代码不能进行aot编译,不过Dart开发了ffi接口后,极大的扩展了dart语言的能力边界,就如同的Java的Jni一样,如果ffi接口开发得足够好用,Dart就能像Python那样成为一门真正的胶水语言。

大家如果有兴趣进一步研究,可以查看dart:ffi包源码,目前该包总共才5个dart文件,源码很少,适合学习。
---------------------

Dart 调用C语言混合编程的更多相关文章

  1. Java语言与C语言混合编程(2)--在Java中调用C语言本地库

    在上一篇文章中介绍了Java语言中的native关键字,以及Java语言调用C语言的编译生成本地动态链接库(DLL)实现加法运算的小例子,本文通过一个更加详细的例子,深入讲解Java语言调用C语言的函 ...

  2. 【转载】ANSYS的APDL与C语言混合编程(实例)

    原文地址:http://www.cnblogs.com/lyq105/archive/2010/05/04/1727557.html 本文讨论的不是利用C语言为ANSYS写扩展(或者说是用户子程序), ...

  3. Java语言与C语言混合编程(1)--Java native 关键字

    一. 什么是 native Method 简单地讲,一个 native Method 就是一个java调用非java代码的接口.一个 native Method 是这样一个java的方法:该方法的实现 ...

  4. Android程序中,内嵌ELF可执行文件-- Android开发C语言混合编程总结

    前言 都知道的,Android基于Linux系统,然后覆盖了一层由Java虚拟机为核心的壳系统.跟一般常见的Linux+Java系统不同的,是其中有对硬件驱动进行支持,以避开GPL开源协议限制的HAL ...

  5. Swift 与 C 语言混合编程

    前言 作为一种可与 Objective-C 相互调用的语言,Swift 也具有一些与 C 语言的类型和特性,如果你的代码有需要,Swift 也提供了和常见的 C 代码结构混合编程的编程方式. 1.基本 ...

  6. SQL+C#:一次多语言混合编程的经验总结

    1.用JAVA做,采取轮询策略: 2.用sql语言+C#混合编程,采取触发策略

  7. 5种语言混合编程:C++、JS、python、Lisp、汇编

    /* 混合C++.JS.python.Lisp.汇编 1种语言,5种语法 */ main { //C++ vector<int> v; v.push(2); putsl(v.size()) ...

  8. Swift语言与Objective-C语言混合编程

    首先创建一个Swift的Single View工程 然后直接在工程中新建OC文件: 然后选择OC语言之后会问你是否自动创建OC和Swift的中间文件: 然后工程文件夹里就有了三个文件: 现在OC头文件 ...

  9. Matlab和C语言混合编程,包含目录的设定

    如果.c文件不依赖于任何第三方库,那么mex编译很简单,只需要在matlab的命令行输入 mex test.c 即可. 但是如果这个c文件使用了第三方库文件,如opencv.gsl等等,那么就需要更改 ...

随机推荐

  1. CSDN挑战编程——《绝对值最小》

    绝对值最小 题目详情: 给你一个数组A[n],请你计算出ans=min(|A[i]+A[j]|)(0<=i,j<n). 比如:A={1, 4, -3}, 则: |A[0] + A[0]| ...

  2. 【树状数组】POJ 2155 Matrix

    附一篇经典翻译,学习 树状数组  http://www.hawstein.com/posts/binary-indexed-trees.html /** * @author johnsondu * @ ...

  3. 【译文】利用STAN做贝叶斯回归分析:Part 2 非正态回归

    [译文]利用STAN做贝叶斯回归分析:Part 2 非正态回归 作者 Lionel Hertzogn 前一篇文章已经介绍了怎样在R中调用STAN对正态数据进行贝叶斯回归.本文则将利用三个样例来演示怎样 ...

  4. kafka备份机制——zk选举leader,leader在broker里负责备份

    Kafka架构 如上图所示,一个典型的kafka集群中包含若干producer(可以是web前端产生的page view,或者是服务器日志,系统CPU.memory等),若干broker(Kafka支 ...

  5. hdu 2063 (二分匹配 匈牙利算法)

    过山车 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...

  6. CodeForces - 810C(规律)

    C. Do you want a date? time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  7. P1850 换教室 概率dp

    其实说是概率dp,本质上和dp没什么区别,就是把所有可能转移的情况全枚举一下就行了,不过dp方程确实有点长... ps:这个题的floyed我竟然之前写跪了... 题目: 题目描述 对于刚上大学的牛牛 ...

  8. 洛谷 P3953 [ NOIP 2017 ] 逛公园 —— 最短路DP

    题目:https://www.luogu.org/problemnew/show/P3953 主要是看题解...还是觉得好难想啊... dfs DP,剩余容量的损耗是边权减去两点最短路差值...表示对 ...

  9. 杂项:BIM

    ylbtech-杂项:BIM 建筑信息化模型(BIM)的英文全称是Building Information Modeling,是一个完备的信息模型,能够将工程项目在全生命周期中各个不同阶段的工程信息. ...

  10. selenium3 + python - table定位

    前言 在web页面中经常会遇到table表格,特别是后台操作页面比较常见.本篇详细讲解table表格如何定位. 一.认识table 1.首先看下table长什么样,如下图,这种网状表格的都是table ...