本篇博客研究Dart语言如何调用C语言代码混合编程,最后咱们实现一个简单示例,在C语言中编写简单加解密函数,使用dart调用并传入字符串,返回加密结果,调用解密函数,恢复字符串内容。git
如未安装过VS编译器,则推荐使用GCC编译器,下载一个64位Windows版本的GCC——MinGW-W64
下载地址github
sjlj
和
seh
后缀表示异常处理模式,
seh
性能较好,但不支持 32位。
sjlj
稳定性好,可支持 32位,推荐下载
seh
版本
将编译器安装到指定的目录,完成安装后,还须要配置一下环境变量,将安装目录下的bin
目录加入到系统Path环境变量中,bin
目录下包含gcc.exe
、make.exe
等工具链。算法
测试环境 配置完成后,检测一下环境是否搭建成功,打开cmd
命令行,输入gcc -v
能查看版本号则成功。sql
去往Dart 官网下载最新的2.3 版本SDK,注意,旧版本不支持ffi
下载地址shell
下载安装后,一样须要配置环境变量,将dart-sdk\bin
配置到系统Path环境变量中。编程
ffi
接口建立测试工程,打开cmd
命令行windows
mkdir ffi-proj
cd ffi-proj
mkdir bin src
复制代码
建立工程目录ffi-proj
,在其下建立bin
、src
文件夹,在bin
中建立main.dart
文件,在src
中建立test.c
文件bash
编写test.c
咱们在其中包含了windows头文件,用于showBox
函数,调用Win32 API,建立一个对话框markdown
#include<windows.h>
int add(int a, int b){
return a + b;
}
void showBox(){
MessageBox(NULL,"Hello Dart","Title",MB_OK);
}
复制代码
进入src
目录下,使用gcc编译器,将C语言代码编译为dll动态库函数
gcc test.c -shared -o test.dll
复制代码
编写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有两个做用,一、去动态库中查找指定的函数;二、将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();
}
}
复制代码
这里写一个稍微深刻一点的示例,咱们在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';
}
复制代码
编译为动态库
gcc encrypt_test.c -shared -o encrypt_test.dll
复制代码
编写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);
}
}
复制代码
运行结果
"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();
}
}
复制代码
修改代码
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);
}
}
复制代码
dart:ffi
包目前正处理开发中,暂时释放的只有基础功能,且使用dart:ffi
包后,Dart代码不能进行aot
编译,不过Dart开发了ffi
接口后,极大的扩展了dart语言的能力边界,就如同的Java的Jni同样,若是ffi
接口开发得足够好用,Dart就能像Python那样成为一门真正的胶水语言。
你们若是有兴趣进一步研究,能够查看dart:ffi
包源码,目前该包总共才5个dart文件,源码不多,适合学习。
参考资料:
欢迎关注个人公众号:编程之路从0到1