zoukankan      html  css  js  c++  java
  • 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-sdkin配置到系统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] = '';
    else r[r_len] = '';

    }

    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] = '';
    else r[r_len] = '';
    }
    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文件,源码很少,适合学习。
    --------------------- 

  • 相关阅读:
    线程状态转换
    CyclicBarrier和CountDownLatch区别
    MySQL事务原理
    DownLoadManager[20530:228829] DiskImageCache: Could not resolve the absolute path of the old directory.
    App各种Icon及Launch image的尺寸和用途
    关于iPhone开发的一些建议
    iPhone6/6Plus下app状态栏内容放大问题处理
    PDF转jpg
    ios开发学习笔记
    nil和Nil和NULL的判断
  • 原文地址:https://www.cnblogs.com/ly570/p/10942293.html
Copyright © 2011-2022 走看看