Dart calls C

This blog examines how Dart calls C code for mixed programming. Finally, we implement a simple example of writing a simple encryption and decryption function in C, using Dart to call and pass in a string, returning the encrypted result, calling the decryption function, and restoring the string contents.

Environment to prepare

Compiler environment

If you have not installed a VS compiler, it is recommended to use the GCC compiler and download a 64-bit version of GCC for Windows — mingw-w64

sjlj
seh
seh
sjlj
seh

Install the compiler to the specified directory. After the installation is complete, you need to configure environment variables. Add the bin directory in the installation directory to the Path environment variable.

After the test environment is configured, check whether the environment is set up successfully. Open the CMD command line interface (CLI) and enter GCC -v to check the version.

The Dart SDK environment

Download the latest version 2.3 SDK from the Dart website. Note that older versions do not support FFI download addresses

After the installation, you also need to configure environment variables, such as darT-SDK \bin in the system Path environment variable.

Test the Dartffiinterface

A simple example

To create the test project, open CMD command line

mkdir ffi-proj
cd ffi-proj
mkdir bin src
Copy the code

Create project directory ffi-proj, create bin and SRC folders, create main.dart file in bin, and create test.c file in SRC

C we include a Windows header file for the showBox function, which calls the Win32 API and creates a dialog box

#include<windows.h>

int add(int a, int b){
    return a + b;
}


void showBox(a){
    MessageBox(NULL."Hello Dart"."Title",MB_OK);
}
Copy the code

Go to the SRC directory and use the GCC compiler to compile THE C language code into a DLL dynamic library

gcc test.c -shared -o test.dll
Copy the code

Write the main dart

import  'dart:ffi'  as ffi;
import  'dart:io'  show Platform;

// Define the method signature according to the function in C (method signature is a description of a method or function, including return value type, parameter type)
/// You need to define two method signatures, one in C and one after conversion to Dart
typedef NativeAddSign = ffi.Int32 Function(ffi.Int32,ffi.Int32);
typedef DartAddSign = int Function(int.int);

/// showBox method signature
typedef NativeShowSign = ffi.Void Function(a);typedef DartShowSign = void Function(a);void main(List<String> args) {
  if (Platform.isWindows) {
    // Load the DLL dynamic library
    ffi.DynamicLibrary dl = ffi.DynamicLibrary.open(".. /src/test.dll");

    // lookupFunction has two functions: 1. 2. Convert Native C functions into Dart Function types
    var add = dl.lookupFunction<NativeAddSign, DartAddSign>("add");
    var showBox = dl.lookupFunction<NativeShowSign, DartShowSign>("showBox");

    // Call add
    print(add(8.9));
	// Call showBoxshowBox(); }}Copy the code

Further usage

For a slightly more in-depth example, we write a simple encryption algorithm in C and then use DART to call C functions to encrypt and decrypt

If you write encrypt_test.c, which is the simplest xor encryption algorithm, you can see that encryption and decryption are actually the same

#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';
}
Copy the code

Compile to a dynamic library

gcc encrypt_test.c -shared -o encrypt_test.dll
Copy the code

Write the main dart

import 'dart:ffi';
import 'dart:io' show Platform;
import "dart:convert";


/// encrypt method signatures. Note that the method signatures of ENCRYPT and decrypt are actually the same. The value types and parameter types returned by both functions are identical
typedef NativeEncrypt = Void Function(CString,CString,Int32);
typedef DartEncrypt = void Function(CString,CString,int);


void main(List<String> args) {
  if (Platform.isWindows) {
    // Load the DLL dynamic library
    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)); }}/// Create a class inheritance Pointer<Int8>Pointer to handle mapping of C language strings to Dart strings
class CString extends Pointer<Int8> {

  /// Apply for memory space and convert the Dart string to A C language string
  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();
  }

 // Apply for the specified heap memory space
  factory CString.malloc(int size) {
    Pointer<Int8> str = allocate(count: size);
    return str.cast();
  }

  /// Convert a C string to a Dart string
  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();
    returnUtf8Decoder().convert(units); }}Copy the code

The results

"helloworld"

Improve the code

The above code does what we set out to do, but there is an obvious memory leak. We allocate heap memory using THE ALLOCATE and malloc elements of CString, but we do not release heap memory manually. After running for a while, we may run out of memory. Here we can only do a simple design to reclaim memory

// create a Reference class to track the memory requested by CString
class Reference {
   final List<Pointer<Void>> _allocations = [];

    T ref<T extends Pointer>(T ptr) {
     _allocations.add(ptr.cast());
     return ptr;
   }

	// Manually release the memory after use
    void finalize() {
	    for (final ptr in_allocations) { ptr.free(); } _allocations.clear(); }}Copy the code

Modify the code

import 'dart:ffi';
import 'dart:io' show Platform;
import "dart:convert";


/// encrypt method signatures. Note that the method signatures of ENCRYPT and decrypt are actually the same. The value types and parameter types returned by both functions are identical
typedef NativeEncrypt = Void Function(CString,CString,Int32);
typedef DartEncrypt = void Function(CString,CString,int);


void main(List<String> args) {
  if (Platform.isWindows) {
    // Load the DLL dynamic library
    DynamicLibrary dl = DynamicLibrary.open(".. /src/hello.dll");
    var encrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("encrypt");
    var decrypt = dl.lookupFunction<NativeEncrypt, DartEncrypt>("decrypt");

	// Create the Reference trace 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));

	// Release it manuallyref.finalize(); }}class CString extends Pointer<Int8> {

  /// open up the memory control to convert the Dart string to a C string
  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(intsize, [Reference ref]) { Pointer<Int8> str = allocate(count: size); ref? .ref(str);return str.cast();
  }

  /// Convert a C string to a Dart string
  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();
    returnUtf8Decoder().convert(units); }}Copy the code

conclusion

The DART: FFI package is currently under development, and only basic functions are released. The DART code cannot be aOT compiled using the DART: FFI package. However, dart’s FFI interface has greatly expanded the dart language’s capabilities, just like Java’s Jni. If the FFI interface is developed well enough, Dart can become a true glue language like Python.

If you are interested in further research, you can check out the source code of the DART: FFI package. Currently, there are only five DART files in the package.

References:

The dart: ffi source code

Dart: Official FFI example

Welcome to my public account: The path of programming from 0 to 1

My personal blog

My lot