Dart 调用C语言混合编程

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环境变量中。

测试Dartffi接口

简单示例

创建测试工程,打开cmd命令行

mkdir ffi-projcdffi-projmkdir bin src复制代码

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

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

#include<windows.h>intadd(inta,intb){returna + b;}voidshowBox(){    MessageBox(NULL,"Hello Dart","Title",MB_OK);}复制代码

进入src目录下,使用gcc编译器,将C语言代码编译为dll动态库

gcc test.c -shared -o test.dll复制代码

编写main.dart

import'dart:ffi'asffi;import'dart:io'show Platform;/// 根据C中的函数来定义方法签名(所谓方法签名,就是对一个方法或函数的描述,包括返回值类型,形参类型)/// 这里需要定义两个方法签名,一个是C语言中的,一个是转换为Dart之后的typedefNativeAddSign = ffi.Int32Function(ffi.Int32,ffi.Int32);typedefDartAddSign =intFunction(int,int);/// showBox函数方法签名typedefNativeShowSign = ffi.VoidFunction();typedefDartShowSign =voidFunction();voidmain(List args) {if(Platform.isWindows) {// 加载dll动态库ffi.DynamicLibrary dl = ffi.DynamicLibrary.open("../src/test.dll");// lookupFunction有两个作用,1、去动态库中查找指定的函数;2、将Native类型的C函数转化为Dart的Function类型varadd = dl.lookupFunction("add");varshowBox = dl.lookupFunction("showBox");// 调用add函数print(add(8,9));// 调用showBox函数showBox();  }}复制代码

深入用法

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

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

#include #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的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同typedefNativeEncrypt = VoidFunction(CString,CString,Int32);typedefDartEncrypt =voidFunction(CString,CString,int);voidmain(List args) {if(Platform.isWindows) {// 加载dll动态库DynamicLibrary dl = DynamicLibrary.open("../src/encrypt_test.dll");varencrypt = dl.lookupFunction("encrypt");vardecrypt = dl.lookupFunction("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指针,用于处理C语言字符串和Dart字符串的映射classCStringextendsPointer{/// 申请内存空间,将Dart字符串转为C语言字符串factoryCString.allocate(StringdartStr) {List units = Utf8Encoder().convert(dartStr);    Pointer str = allocate(count: units.length +1);for(inti =0; i < units.length; ++i) {      str.elementAt(i).store(units[i]);    }    str.elementAt(units.length).store(0);returnstr.cast();  }// 申请指定大小的堆内存空间factoryCString.malloc(intsize) {    Pointer str = allocate(count: size);returnstr.cast();  }/// 将C语言中的字符串转为Dart中的字符串staticStringfromUtf8(CString str) {if(str ==null)returnnull;intlen =0;while(str.elementAt(++len).load() !=0);List units =List(len);for(inti =0; i < len; ++i) units[i] = str.elementAt(i).load();returnUtf8Decoder().convert(units);  }}复制代码

运行结果

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

完善代码

上述代码虽然实现了我们的目标,但是存在明显的内存泄露,我们使用CString

的allocate和malloc申请了堆内存,但是却没有手动释放,这样运行一段时间后可能会耗尽内存空间,手动管理内存往往是C/C++中最容易出问题的地方,这里我们只能进行一个简单的设计来回收内存

/// 创建Reference 类来跟踪CString申请的内存classReference{finalList> _allocations = [];    T ref(T ptr) {    _allocations.add(ptr.cast());returnptr;  }// 使用完后手动释放内存voidfinalize() {for(finalptrin_allocations) {      ptr.free();    }    _allocations.clear();  }}复制代码

修改代码

import'dart:ffi';import'dart:io'show Platform;import"dart:convert";/// encrypt函数方法签名,注意,这里encrypt和decrypt的方法签名实际上是一样的,两个函数返回值类型和参数类型完全相同typedefNativeEncrypt = VoidFunction(CString,CString,Int32);typedefDartEncrypt =voidFunction(CString,CString,int);voidmain(List args) {if(Platform.isWindows) {// 加载dll动态库DynamicLibrary dl = DynamicLibrary.open("../src/hello.dll");varencrypt = dl.lookupFunction("encrypt");vardecrypt = dl.lookupFunction("decrypt");// 创建Reference 跟踪CStringReference 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();  }}classCStringextendsPointer{/// 开辟内存控件,将Dart字符串转为C语言字符串factoryCString.allocate(StringdartStr, [Reference ref]) {List units = Utf8Encoder().convert(dartStr);    Pointer str = allocate(count: units.length +1);for(inti =0; i < units.length; ++i) {      str.elementAt(i).store(units[i]);    }    str.elementAt(units.length).store(0);    ref?.ref(str);returnstr.cast();  }factoryCString.malloc(intsize, [Reference ref]) {    Pointer str = allocate(count: size);    ref?.ref(str);returnstr.cast();  }/// 将C语言中的字符串转为Dart中的字符串staticStringfromUtf8(CString str) {if(str ==null)returnnull;intlen =0;while(str.elementAt(++len).load() !=0);List units =List(len);for(inti =0; i < len; ++i) units[i] = str.elementAt(i).load();returnUtf8Decoder().convert(units);  }}复制代码

总结

dart:ffi包目前正处理开发中,暂时释放的只有基础功能,且使用dart:ffi包后,Dart代码不能进行aot编译,不过Dart开发了ffi接口后,极大的扩展了dart语言的能力边界,就如同的Java的Jni一样,如果ffi接口开发得足够好用,Dart就能像Python那样成为一门真正的胶水语言。

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

参考资料:

http://www.developcls.com/qa/405b4a822f36458481346daf59ad6a90.html

dart:ffi 源码

dart:ffi 官方示例

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容