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環境變量中。

測試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文件,源碼很少,適合學習。
--------------------- 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM