How to use Dart FFI for writing C code

Search for a command to run...

No comments yet. Be the first to comment.
While I generally write about Flutter. This problem has been bugging me for a while and there seem to be limited resources to find a solution. So for anyone who is looking for a solution. Here you go: Type edge:flags into the address bar and open it...
In this tutorial, weāll go through covering interceptor, which is a very crucial feature of Dio. You can find the entire source code for the project here: dio_tasker Understanding Interceptors: What is an interceptor? An interceptor in Dio is a powe...

In this part, weāll enhance our Task Manager app by parsing JSON responses and handling different types of errors. Weāll use Dioās built-in error handling mechanisms. You can find the source code of the entire app here: dio_tasker Parsing JSON Respon...

While building apps in Flutter is extremely fluid and wonderfully intuitive. We obviously know that we canāt build the next TikTok or Twitter by only building offline apps. Which is why we absolutely need internet capabilities in our Flutter apps. Th...

Understanding when to use setState in Flutter is crucial for managing our appās state effectively. Hereās a detailed guide: When to use setState ? Updating the UI: Use setState when we need to update the UI in response to changes in the internal sta...

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.
You can find the source code for here: ffi_factorial
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
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
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];
}
}
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
ffi_factorial.dart:Open bin/my_ffi_project.dart and add the following imports:
import 'dart:ffi';
import 'dart:io';
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 them on top of our main().
// 2. Define FFI Function Signature
typedef NativeFactorialMemoFunc = Int32 Function(Int32 n);
typedef DartFactorialMemoFunc = int Function(int n);
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');
// 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
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 š¦