Dart 調用C語言混合編程

Dart 調用C語言

本篇博客研究Dart語言如何調用C語言代碼混合編程,最後我們實現一個簡單示例,在C語言中編寫簡單加解密函數,使用dart調用並傳入字符串,返回加密結果,調用解密函數,恢復字符串內容。

環境準備

編譯器環境

如未安裝過VS編譯器,則推薦使用GCC編譯器,下載一個64位Windows版本的GCC——MinGW-W64
下載地址

在這裏插入圖片描述
如上,它有兩個版本,sjljseh後綴表示異常處理模式,seh 性能較好,但不支持 32位。 sjlj 穩定性好,可支持 32位,推薦下載seh 版本

將編譯器安裝到指定的目錄,完成安裝後,還需要配置一下環境變量,將安裝目錄下的bin目錄加入到系統Path環境變量中,bin目錄下包含gcc.exemake.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

創建工程目錄ffi-proj,在其下創建binsrc文件夾,在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);
}

進入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有兩個作用,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();
  }
}

在這裏插入圖片描述

深入用法

這裏寫一個稍微深入一點的示例,我們在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 的allocatemalloc申請了堆內存,但是卻沒有手動釋放,這樣運行一段時間後可能會耗盡內存空間,手動管理內存往往是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文件,源碼很少,適合學習。

參考資料:
dart:ffi 源碼
dart:ffi 官方示例

歡迎關注我的公衆號:編程之路從0到1
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章