We will cover briefly about
- Creating C Library
- Call from Flutter on Linux
- Passing parameter from Dart to C
- 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
- 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
- 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
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.
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
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
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);
Top comments (1)
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...