DEV Community

loading...
Cover image for Dart FFI and Linux

Dart FFI and Linux

aseem wangoo
Keen learner and tech enthusiast...🤩👨🏼‍💻. If you find my work helpful some time, somewhere pass me a coffee!!
・10 min read

In case it helped :)
Pass Me A Coffee!!

We will cover briefly about

  1. Creating C Library
  2. Call from Flutter on Linux
  3. Passing parameter from Dart to C
  4. Passing structs from Dart to C

Note: We won’t be explaining in-depth about Dart FFI as there are good articles dedicated to it. 

Creating C Library on Linux

We are going to create the following use cases

  1. Generate random number from C

Let’s create a c file named random_number.c For generating random number in c, we use rand. Here is the snippet

int fetch_number()
{
    int c, n;
    srand(time(0));
    n = rand() % 100 + 1;
    return n;
}

Note: We are capping the values from 0 to 100

We will create a header file randomnumber.h, and paste the declaration of our function inside it.

// randomnumber.h
int fetch_number();

Inside our random_number.c, we will import this header file and implement the definition of fetch_number . Our final file looks like this

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "randomnumber.h"
int main()
{
    return 0;
}
// Definition omitted for brevity. Basically, it's the above function only :)

Let’s test our c function, navigate to the folder containing the program and run

gcc random_number.c -o random_number

This generates our output file as random_number.o Run this output file by

./random_number

Output from C library
Output from C library

  • Next, we will create our dynamic library (librandomnumber.so)

To create a dynamic library, write the following command:

gcc -shared -o librandomnumber.so random_number.o
// librandomnumber.o is the name of your dynamic library
// random_number.o is the object file created from the above step

Note: The library name can be anything, but should start with “lib” and end with  .so

The compiler will later identify a library by searching for files beginning with “lib” and ending with the naming convention, .so

Dynamic Library in Linux
Dynamic Library in Linux

Call from Flutter on Linux

Drag and drop the library (librandomnumber.so) in our flutter project. We create a library subfolder and drop it there.

Libraries inside Flutter project
Libraries inside Flutter project

Steps to call a function from C
Steps to call a function from C

We will call our dynamic libraries from Flutter Desktop now using Dart FFI in Linux

  • Import the dart ffi package(present inside Flutter) as
import 'dart:ffi'

This has a class DynamicLibrary. We call the method open and load our dynamic library (librandomnumber.so).

final dyLib = DynamicLibrary.open(
'lib/library/linux/librandomnumber.so',
);

Lookup for the symbol from the loaded dynamic library using lookup or lookupFunction.

dyLib.lookup<NativeFunction<fetch_number_func>>(
   'fetch_number',
);
// This returns a pointer

Note: The lookup takes in a native function (which represents a function in C)

Our fetch_number_func is basically

typedef fetch_number_func = Int32 Function();

and this Int32 comes from dart:ffi, which represents a native signed 32 bit integer in C.

  • The pointer returned from the above step is converted into Dart function
final number = fetchNumberPointer.asFunction<FetchNumber>();
number(); // Call the function

where FetchNumber is our function

typedef FetchNumber = int Function();

Passing parameter from Dart to C

Let’s create a c file named string_ops.c We will define a function, which takes in a string parameter, reverses it, and sends it back. Here is the snippet

void reverse(char *str) {
  // Calculate length of string.
  int l = 0;
  while (*(str + l) != '\0')
    l++;
// Initialize.
  int i;
  char *first, *last, temp;
  first = str;
  last = str;
  for (i = 0; i < l - 1; i++) {
    last++;
  }
// Iterate from both ends, swapping first and last as we go.
  for (i = 0; i < l / 2; i++) {
    temp = *last;
    *last = *first;
    *first = temp;
first++;
    last--;
  }
}

As seen in the above section, we will create a header file stringops.h and paste the declaration of our function inside it.

// stringops.h
void reverse(char *str);

Inside our string_ops.c, we will import this header file and implement the definition of reverse . Our final file looks like this

#include<stdio.h>
#include "stringops.h"
int main()
{
 return 0;
}
// Definition omitted for brevity. Basically, it's the above function only :)

Finally, we create our header file, libstringops.so (see the steps above)

Call from Flutter

We copy-paste this header file inside our flutter project

Libraries inside Flutter project
Libraries inside Flutter project

Open the dynamic library using the dart ffi

final dylib = DynamicLibrary.open(
   'lib/library/linux/libstringops.so',
);

Now we need to pass in the parameter, and this should be something that C understands. In short, we need to convert our Flutter Strings to Pointer.

Time to include package FFI

Our input (let's say a string) can be converted to UTF-8 pointer using toNativeUTF8,

String input = 'hello world';
final inputToUtf8 = input.toNativeUtf8();

Next, we need to look up the function

final reverse = dylib.lookupFunction<Void Function(Pointer<Utf8>),
        void Function(Pointer<Utf8>)>('reverse');

Note: First param is the C function signature, and second param is the Dart function signature.

Now, we have a function that takes in a parameter of Pointer<Utf8> and we pass in our inputToUtf8 

reverse(inputToUtf8);
final resp = inputToUtf8.toDartString();

Finally, the response(which is of type UTF-8) is converted toDartString

  • One last thing, toNativeUtf8 uses an allocator to allocate C memory. Remember, in C we need to explicitly free the memory used.
  • This is done by calling free 
malloc.free(inputToUtf8);

Passing structs from Dart to C

In the header file stringops.h, let’s add in our struct

struct Name
{
    char *firstname;
    char *lastname;
};
struct Name create_name(char *firstname, char *lastname);

Inside our string_ops.c, we have already imported this header file. Let’s implement the definition of create_name . Our final file looks like this

#include<stdio.h>
#include "stringops.h"
int main()
{
 return 0;
}
struct Name create_name(char *firstname, char *lastname) {
 struct Name name;
 name.firstname = firstname;
 name.lastname = lastname;
 return name; 
}

Finally, we create our header file, libstringops.so (see the steps above)

Call from Flutter

Note: We require Dart 2.12 and onwards to call structs from ffi

We copy-paste this header file inside our flutter project

Libraries inside Flutter project
Libraries inside Flutter project

We already have our dynamic library opened (see the above section), now we need to look up our create_name function.

final createName =
  dylib.lookupFunction<CreateNameNative, CreateName>('create_name');

As we know now, the lookup function takes in a C function and a Dart function. Our functions look like

// For structs
typedef CreateNameNative = Name Function(
    Pointer<Utf8> firstName, Pointer<Utf8> lastName);
typedef CreateName = Name Function(
    Pointer<Utf8> firstName, Pointer<Utf8> lastName);

since, we need to pass in the two parameters

Now, here is something new, we have Name 

class Name extends Struct {
  external Pointer<Utf8> firstName;
  external Pointer<Utf8> lastName;
}
  • Dart introduces Struct, the supertype of all FFI struct types.
  • FFI struct types should extend this class and declare fields corresponding to the underlying native structure.
  • All field declarations in a Struct subclass declaration must be marked external

Rest is the same, we pass in our inputs as toNativeUtf8

String first = 'Aseem';
String second = 'Wangoo';
final fNameUtf8 = first.toNativeUtf8();
final lNameUtf8 = second.toNativeUtf8();
final name = createName(fNameUtf8, lNameUtf8);
// createName is from the above

Finally, we have our response in the variable name The response(which is of type UTF-8) is converted toDartString

final fname = name.firstName.toDartString();
final lname = name.lastName.toDartString();

The memory is freed using free

malloc.free(fNameUtf8);
malloc.free(lNameUtf8);

Source Code

In case it helped :)
Pass Me A Coffee!!

Discussion (1)

Collapse
pablonax profile image
Pablo Discobar

Helpful article! Thanks! If you are interested in this, you can also look at my article about Flutter templates. I made it easier for you and compared the free and paid Flutter templates. I'm sure you'll find something useful there, too. - dev.to/pablonax/free-vs-paid-flutt...