本篇博客研究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