How to use Dart FFI for writing C code
What is FFI?
Foreign Function Interface (FFI) allows a program written in one language to call functions or services written in another language.
In Dart FFI is used to call native C functions and libraries, thus enhancing the app's capabilities by utilizing existing code or libraries.
Dart FFI can be used with any language that can expose a C-compatible API like C, C++, Rust and to some extent even Python3.
Use Cases:
Reuse existing libraries.
Performing tasks that require high performance.
Accessing platform-specific features not exposed by Flutter.
Let's create a basic FFI.
Source Code:
You can find the source code for here: ffi_factorial
Dart FFI Factorial App:
We'll create two basic Dart Apps that runs on Windows, Linux and Mac as a CLI app to get factorial.
The first app will have give us factorial of a static number like 5 or 10.
The second app will provide us a factorial based on the user input.
Also you can find the source code for the app here: ffi_factorial
Step 1. Create the Dart App:
Let's create a new dart project:
dart create ffi_factorial
cd ffi_factorial/bin/
Now if we look at the files inside the Dart project we get this:
ls
ffi_factorial.dart
Step 2. Create factorial.c
inside our bin/
Let's write our C code (e.g., factorial.c
) containing the functions you want to access from Dart. Remember to write inside our bin/ folder for easy access.
#include <stdio.h>
// factorial.c using memoization to get the time complexity to O(n)
// Array to store memoized results (adjust size as needed)
int memo[100];
int factorial_memo(int n) {
if (n == 0) {
return 1;
} else if (memo[n] != 0) {
return memo[n]; // Return memoized result
} else {
memo[n] = n * factorial_memo(n - 1);
return memo[n];
}
}
Step 3. Create Dynamic library.
Now if we want to access our C Code inside our Dart function. We need to create a dynamic library.
Now since each operating system is different we'll have to create separate dynamic library files for each OS.
Operating System and Library Name:
macOS: The library file will be named
libfactorial.dylib
.Linux: The library file will be named
libfactorial.so
.Windows: The library file will be named
factorial.dll
.
Compile your C code into a dynamic library:
Linux:
gcc -shared -o
libfactorial.so
factorial.c -fPIC
Windows:
gcc -shared -o factorial.dll factorial.c
macOS:
gcc -dynamiclib -o libfactorial.dylib factorial.c
Note: When I say different OS, I mean target Operating Systems, not where you are compiling the code from.
For instance although I am writing all of this code in macOS if I want it to run on all 3 platforms, then I'll have to create all 3 files.
So I am running all three commands:
gcc -dynamiclib -o libfactorial.dylib factorial.c
gcc -shared -o factorial.dll factorial.c
gcc -shared -o libfactorial.so factorial.c -fPIC
Now here are the current files:
ls
factorial.c factorial.dll ffi_factorial.dart libfactorial.dylib libfactorial.so
Step 4. Call Native Functions from our ffi_factorial.dart
:
Open bin/my_ffi_project.dart
and add the following imports:
import 'dart:ffi';
import 'dart:io';
- Now within the
main()
load the dynamic libraries:
As you can see we are ensuring that the correct file is opened in correct os
// 1. Load the Dynamic Library (dylib)
// Ensure to use the correct extension for your platform (.dll for Windows, .dylib for macOS, .so for Linux)
final dylib = Platform.isWindows
? DynamicLibrary.open('libfactorial.dll')
: Platform.isMacOS
? DynamicLibrary.open('libfactorial.dylib')
: DynamicLibrary.open('libfactorial.so'); // Linux
- Create typedefs to represent the the C function signatures in Dart:
Create them on top of our main()
.
// 2. Define FFI Function Signature
typedef NativeFactorialMemoFunc = Int32 Function(Int32 n);
typedef DartFactorialMemoFunc = int Function(int n);
- Use the
lookup
method to get a reference to the C function within the loaded library:
// 3. Look Up the Native Function
final DartFactorialMemoFunc nativeFactorialMemo =
dylib.lookupFunction<NativeFactorialMemoFunc, DartFactorialMemoFunc>('factorial_memo');
- Finally call the native function
// 4. Call the Native Function (from Dart)
final result = nativeFactorialMemo(5);
print('Factorial of 5 is: $result'); // Output: 120
Here's the entire code:
import 'dart:ffi';
import 'dart:io';
// 2. Define FFI Function Signature
typedef NativeFactorialMemoFunc = Int32 Function(Int32 n);
typedef DartFactorialMemoFunc = int Function(int n);
void main(List<String> arguments) {
// 1. Load the Dynamic Library (dylib)
// Ensure to use the correct extension for your platform (.dll for Windows, .dylib for macOS, .so for Linux)
final dylib = Platform.isWindows
? DynamicLibrary.open('libfactorial.dll')
: Platform.isMacOS
? DynamicLibrary.open('libfactorial.dylib')
: DynamicLibrary.open('libfactorial.so'); // Linux
// 3. Look Up the Native Function
final DartFactorialMemoFunc nativeFactorialMemo =
dylib.lookupFunction<NativeFactorialMemoFunc, DartFactorialMemoFunc>('factorial_memo');
// 4. Call the Native Function (from Dart)
final result = nativeFactorialMemo(5);
print('Factorial of 5 is: $result'); // Output: 120
}
So if we run the app. We get the following output:
dart run ffi_factorial.dart
# Factorial of 5 is: 120
Factorial Using User Input:
Let's create a new file named factorial_user_input.dart
inside our bin/
code factorial_user_input.dart
Go ahead and copy the code from our existing ffi_factorial.dart
.
Now since we want to get the value from user terminal: We'll use stdin.readLineSync()
. Here is our new code:
import 'dart:ffi';
import 'dart:io';
// 2. Define FFI Function Signature
typedef NativeFactorialMemoFunc = Int32 Function(Int32 n);
typedef DartFactorialMemoFunc = int Function(int n);
void main(List<String> arguments) {
// 1. Load the Dynamic Library (dylib)
// Ensure to use the correct extension for your platform (.dll for Windows, .dylib for macOS, .so for Linux)
final dylib = Platform.isWindows
? DynamicLibrary.open('libfactorial.dll')
: Platform.isMacOS
? DynamicLibrary.open('libfactorial.dylib')
: DynamicLibrary.open('libfactorial.so'); // Linux
// 3. Look Up the Native Function
final DartFactorialMemoFunc nativeFactorialMemo =
dylib.lookupFunction<NativeFactorialMemoFunc, DartFactorialMemoFunc>('factorial_memo');
// Get user input
print('Enter a number to calculate its factorial: ');
final input = stdin.readLineSync();
if (input != null) {
final number = int.tryParse(input);
if (number != null) {
// 4. Call the Native Function (from Dart)
final result = nativeFactorialMemo(number);
print('Factorial of $number is: $result'); // Output: 120 (for 5)
} else {
print('Please enter a valid integer.');
}
} else {
print('Input cannot be null.');
}
}
Here's the output:
dart run factorial_user_input.dart
# Enter a number to calculate its factorial:
# 23
# Factorial of 23 is: 862453760
Perfect. So this covers our introduction to FFI. I'll keep writing more about FFI and platform channels as these are some of the most interesting aspects of the flutter app. So please subscribe to my blog for more interesting Flutter posts.
And Happy Coding 🐦
Subscribe to my newsletter
Read articles from Harish Kunchala directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by